操作系统精髓与设计原理学习笔记五:并发性(互斥和同步)

并发是所有问题的基础,也是操作系统设计的基础。并发包括很多设计问题,其中有进程间通信资源共享与竞争,多个进程活动的同步以及分配给进程的处理器时间等。

支持并发进程的鸡巴需求是加强互斥能力。也就是说,当一个进程被授予互斥能力时,那么在其活动期间,它具有排斥所有其他进程的能力。

主要的三种方法:信号量,管程,消息传递。


零、与并发相关的关键术语

1、原子操作:一个函数货动作由一个或多个指令的序列实现,对外是不可见的;也就是说,没有其他进程可以看到其中间状态货中断此操作。要保证指令的序列要么作为一个组来执行,要么都不执行。原子性保证了并发进程的隔离。

2、临界区:是一段代码,在这段代码中进程将访问共享资源,当另外一个进程已经在这段代码中运行时,这个进程就不能在这段代码中执行。

3、死锁:两个或两个以上的进程因其中的每个进程都在等待其他进程做完某些事情而不能继续执行。

4、互斥:当一个进程在临界区访问共享资源时,其他进程不能进入该临界区访问任何共享资源。

5、竞争条件:多个线程货进程在读写一个共享数据时,结果依赖于他们执行的相对时间,这种情形称为竞争条件。

6、饥饿:是指一个可运行的进程尽管能继续执行,但被调度程序无限期的忽视,而不能被调度执行的情形。


一、并发的原理

在单处理器多道程序设计系统中,进程被交替执行,表现出一种并发执行的外部特征。在多处理器系统中,不仅可以交替执行进程,而且可以重叠执行进程。

涉及的困难:1)全局资源的共享充满了危险;2)操作系统很难对资源进行优化分配。一个多处理器系统还必须处理多个进程同时执行所引发的问题。

1、一个例子

共享变量的问题例子:问题的本质在于共享全局变量。多个进程访问这个全局变量,如果一个进程修改了它,然后被中断,另一个进程可能在第一个进程使用它的值之前又修改了这个变量。在单处理器系统的情况下,出现问题的原因是中断可能会在进程中的任何地方停止指令的执行;在多处理器系统的情况下,不仅同样的条件可以引发问题,而且当两个进程同事执行并且都试图访问同一个全局变量时,也会引发问题。

解决思路:控制对共享变量的访问。上面的例子说明,如果需要保护共享的全局变量(及其他共享的全局资源),唯一的办法是控制访问该变量的代码。如果我们定义了一条规则,一次只允许一个进程进入echo,并且只有在echo过程允许结束后,他才对另一个进程是可用的,那么刚才讨论的那类问题就不会发生了。

2、竞争条件

竞争条件发生在多个进程或线程读写数据时,其最终的结果依赖于多个进程的指令执行顺序。

3、操作系统关注的问题

1)操作系统必须能够跟踪不同的进程;

2)操作系统必须为每个活跃的进程分配和释放各种资源;

3)操作系统必须包含每个进程的数据和物理资源,避免其他进程的无意干涉;

4)一个进程的功能和输出结果必须与其相对于其他并发进程的执行速度无关。

4、进程的交互

三种方式:进程之间相互不知道对方的存在(竞争情况);进程通过共享某些对象的方式间接知道对方的存在;进程之间知道对方的存在(这些进程可以通过进程ID互相通信)。

1)进程间的资源竞争

当并发进程竞争使用同一组员时,他们之间会发生冲突。竞争进程间没有任何信息交换,但是一个进程的执行可能会影响到竞争进程的行为。

竞争进程面临的三个控制问题:a)互斥的要求。假设两个或更多的进程需要访问一个不可共享的资源,我们把这类资源称为临界资源,使用临界资源的那一部分程序称为程序的临界区。一次只允许有一个程序在临界区中,这一点非常重要。b)死锁 。c)饥饿。

由于操作系统负责分配资源,竞争的控制不可避免的设计操作系统。此外,进程自身需要能够以某种方式表达互斥的要求,比如在使用前对资源加锁,但任何一种方案都设计到操作系统的某些支持,如提供锁机制。

2)进程间通过共享的合作

通过共享进行合作的情况,包括进程间在互相并不确切知道对方的情况下进行交互。多个进程可能访问一个共享变量、共享文件或数据库,进程可能使用并修改共享变量而并不涉及其他进程,但却知道其他进程也可能访问同一个数据。因此,这些进程必须合作,以确保它们共享的数据得到正确管理。

由于数据保存在资源中,因此再次涉及有关互斥、死锁和饥饿等控制问题。唯一的却别是可以以两种不同的模式(读和写)访问数据项,并且只有写操作必须保持互斥。

对于数据一致性的要求,在通过共享进行合作的情况下,临界区的概念是非常重要的。

3)进程间通过通信的合作

在竞争的情况下,进程在不知道其他进程存在的情况下共享资源

在共享合作的情况下,进程共享变量,每个进程并未明确的知道其他进程的存在,它只知道需要维护数据的完整性。

当进程通过通信进行合作时,哥哥进程都与其他进程直接进行连接,通信提供了同步和协调各种活动的方法。在典型情况下,通信科可由各种类型的消息组成,发送消息和接受消息的原语由程序设计语言提供或者由操作系统的内核提供

5 实现互斥的几种方法

1)软件方法:让由并发执行的进程担负实现互斥的责任,这类进程,不论是系统程序还是应用程序,都需要与另一个进程合作,二不需要程序设计语言或操作系统通过任何支持来实施互斥。

2)第二种方法涉及专门的机器指令。

3)在操作系统货程序设计语言中提供某种级别的支持。


二、互斥-硬件的支持

1 中断禁用

在单处理器机器中,并发进程不能重叠,只能交替。此外,一个进程将一直运行,直到它调用了一个系统服务或被中断。因此为保证互斥,只需要保证一个进程不被中断就可以了,这种能力可以通过系统内核为启用和禁用中断定义的原语来提供。但该方法的代价非常高,而且不能用于多处理器结构。

2专用机器指令


三、信号量

信号量:用于进程间传递信号的一个整数值。在信号量上只有三种操作可以进行:初始化、递减和增加。这三种操作都是原子操作。递减操作可以用于阻塞一个进程,增加操作可以用于解除阻塞一个进程。

两个或多个进程可以通过简单的信号进行合作,一个进程可以被迫在某一位置停止,直到它接受到一个特定的信号。任何复杂的合作需求都可以通过适当的信号结构得到满足。为了发信号,需要使用一个称为信号量的特殊变量。为通过信号量s传送信号,进程可执行原语semSignal(s);为通过信号量s接收信号,进程可执行原语semWait(s)。如果相应的信号仍没有发送,则进程被阻塞,直到发送完为止。

可以把信号量视为一个具有整数值的变量,在它之上定义三个操作:

1)一个信号量可以初始化成非负数;

2)semWait操作使信号量减1。若值为负数,则执行semWait的进程被阻塞。否则进程继续执行。

3)semSignal操作使信号量加1.若值小于或等于零,则被semWait操作阻塞的进程被解除阻塞。

对这三种草垛的解释如下:开始时,信号量的值为零或正数。如果该值为正数,则该值等于发出semWait操作后可立即继续执行的进程的数量。如果该值为零,则发出semWait操作的下一个进程会被阻塞,此时该信号量的值变为负值。之后,每个后续的semWait操作都会使信号量的负值更大。该负值等于正在等待解除阻塞的进程的数量。在信号量为负值的情形下,每一个semSignal操作都会将等待进程中的一个进程解除阻塞。。

信号量需要使用队列来保存在信号量上等待的进程。最公平的策略是先进先出,被阻塞时间最久的进程最先从队列释放(强信号量,保证不会出现饥饿);没有规定进程从队列中移出顺序的信号量称为弱信号量

1 互斥

semWait(s)

/*临界区*/

semSignal(s)

/*其他部分*/

信号量一般初始化为1,这样第一个执行semWait的进程可以立即进入临界区,并把s的值置为0。

接着任何试图进入临界区的其他进程,豆浆发现第一个进程忙(semWait后s<0),因此被阻塞,把s的值减1。

当最初进入临界区的进程离开时,s增1,一个被阻塞的进程(如果有的话)被移出等待队列,置于就绪态,这样,当操作系统下一次调度时,它可以进入临界区。

2 生产者、消费者问题

有一个活多个生产者生产某种类型的数据(记录、字符),并放置在缓冲区中;有一个消费者从缓冲区中取数据,每次取一项;系统保证避免对缓冲区的重复操作,也就是说,在任何时候只有一个主体(生产者或消费者)可以访问缓冲区。问题是要确保这种情况,当缓存已满时,生产者不会继续向其中添加数据;当缓存为空时,消费者不会从中移走数据。

需要三个信号量empty和full用于同步缓冲区,而mut变量用于在访问缓冲区时是互斥的。

 private static Mutex mut = new Mutex();//临界区信号量
 private static Semaphore empty = new Semaphore(5, 5);//只有占用该信号量时生产者才能生产,表示有缓冲区中有空闲位置,初始时全都是空闲的
 private static Semaphore full = new Semaphore(0, 5);//只有占用该信号量时消费者才能消费,表示缓冲区中有数据,初始时一个数据都没有

  private static void Producer()
         {
             Console.WriteLine("{0}已经启动",Thread.CurrentThread.Name);
             empty.WaitOne();//对empty进行P操作
             mut.WaitOne();//对mut进行P操作
             Console.WriteLine("{0}放入数据到临界区", Thread.CurrentThread.Name);
                 Thread.Sleep(1000);
             mut.ReleaseMutex();//对mut进行V操作
             full.Release();//对full进行V操作
         }

         private static void Customer()
         {
             Console.WriteLine("{0}已经启动", Thread.CurrentThread.Name);
             Thread.Sleep(12000);
             full.WaitOne();//对full进行P操作
             mut.WaitOne();//对mut进行P操作
             Console.WriteLine("{0}读取临界区", Thread.CurrentThread.Name);
             mut.ReleaseMutex();//对mut进行V操作
             empty.Release();//对empty进行V操作
         }

3 信号量的实现

问题的本质是互斥,任何时候只有一个进程可以用semWait或semSignal操作控制一个信号量。

对于但处理器而系统,在semWait或semSignal操作期间是可以禁用中断的。

semWait(s){

  禁用中断

  s.count--;

  if(s.count<0){

   该进程进入s.queue队列

   阻塞该进程,并允许中断

  }

  else

    允许中断

}

semSignal(s){

  禁用中断;

  s.count++;

  if(s.count<=0){

    从s.queu队列中移出进程P

    进程P进入就绪队列

  }

}


四、管程

管程是由一个或多个过程、一个初始化序列和局部数据组成的软件模块,其主要特点如下:

1)局部数据变量只能被管程的过程访问,任何外部过程都不能访问;

2)一个进程通过调用管程的一个过程进入管程;

3)在任何时候,只有一个进程在管程中执行,调用管程的任何其他进程都被阻塞,以等待管程可用。

管程可以提供一种互斥机制:管程中的数据变量每次只能被一个进程访问到。因此,可以把一个共享数据结构放在管程中,从而提供对它的保护。如果管程中的数据代表某些资源,那么管程为访问这些资源提供了互斥机制。

管程通过使用条件变量(一种数据类型,用于阻塞进程或现场,直到特定的条件为真)提供对同步的支持,这些条件变量包含在管程中,并且只有在管程中才能被访问。有两个函数可以操作条件变量:

1)cwait(c):调用进程的执行在条件c上阻塞,管程现在可以被另一个进程调用。

2)csignal(c):恢复执行在cwait之后因为某些条件而阻塞的进程。如果有多个这样的进程,选择其中一个;如果没有这样的进程,什么都不做。

当一个进程在管程中时,他可能通过发送cwait(x)把自己暂时阻塞在条件x上,随后他被放入等待条件改变以重新进入管程的进程对了中,在cwait(x)调用的吓一跳指令开始恢复执行。如果在管程中执行的一个进程发现条件变量x发生了变化,它将发送csignal(x),通知相应的条件队列条件已经改变。

与信号量相比,管程担负的责任不同。对于管程,它构造了自己的互斥机制:生产者和消费者不可能同时访问缓冲区;但是程序猿必须把适当的cwait和csignal原语放在管程中,用于防止进程往一个满缓冲区中存放数据项,或者从一个空缓冲区中取数据项(同步)。而在使用信号量的情况下,执行互斥和同步都属于程序猿的责任。


五、消息传递

进程交互时,必须满足两个基本要求:同步和通信。为实施互斥,进程间需要同步;为了合作,进程间需要交换信息,提供这些功能的一种方法是消息传递。消息传递可以再分布式系统,共享内存的多处理器系统和单处理器系统中实现。

消息传递的实际功能以一对原语的形式提供:

send(destination,message)

receive(source,message)

这是进程间进行消息传递所需要的最小操作集。一个进程以消息的形式给另一个指定的目标进程发送信息;进程通过执行receive原语接受信息,receive原语中指明发送消息的源进程和消息

1 同步

两个进程间的消息通信隐含着某种同步信息:只有当一个进程发送消息后,接受者才能接受消息。发送者和接受者都可以阻塞或不阻塞,通常有三个组合:

1)阻塞send,阻塞receive

2)无阻塞send,阻塞receive

3)无阻塞send,无阻塞receive

2 寻址

在send和receive原语中确定目标或源进程的方案可分为两类:直接寻址和间接寻址。

对于直接寻址,send原语包含目标进程的标识号,而receive原语有两种处理方式。一种是要求进程显示的指定源进程,因此,该进程必须事先知道希望得到来自哪个进程的信息(这种方式对于处理并发进程间的合作是非常有效的);另一种情况是不可能指定所期望的源进程。

对于间接寻址,通过解除发送者和接受者之间的耦合关系,在消息的使用上允许更大的灵活性。在这种情况下,消息不是知己从发送者发送到接受者,而是发送到一个共享数据结构,该结构由临时保存消息的队列组成,这些队列通常称为信箱。因此,对两个通信进程,一个进程给合适的信箱发送消息,另一个进程从信箱中获得这些消息。

3 互斥

希望进入临界区的进程首先试图接收一条消息,如果信箱为空,则该进程被阻塞;一旦进程获得消息,它执行它的临界区,然后把该消息放回信箱。因此,消息函数可以视为在进程之间传递的一个令牌。

1)如果有一条消息,它仅仅被传递给一个进程,其他进程被阻塞;

2)如果消息队列为空,那么所有进程被阻塞;当有一条消息可用时,只有一个阻塞进程被激活并得到这条消息。


六、读者写者问题

问题描述:

1)任意多的读进程可以同时读这个文件;

2)一次只有一个写进程可以写文件;

3)如果一个写进程正在写文件,那么禁止任何读进程读文件。

1 读者优先

2 写者优先


七、小结

现代操作系统的核心是多道程序设计、多处理器和分布式处理器。这些方案的基础及操作系统设计技术的基础是并发。当多个进程并发执行时,不论是在多处理器系统的情况下,还是在单处理器多道程序系统中,都会产生冲突和合作的问题。

互斥指的是,对一组并发进程,一次只有一个进程能够访问给定的资源或执行给定的功能。互斥技术可以用于解决诸如资源征用之类的冲突,还可以用于进程间的同步,使得它们可以合作。

支持互斥的另一种方法是在操作系统中提供相应功能,其中最常见的两种技术是信号量消息机制。信号量用于在进程间发信号,并可以很容易的实施一个互斥协议;消息对实施互斥是很有用的,它还为进程间的通信提供了一种有效的方法。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值