经典进程同步互斥问题
廖家龙 用心听,不照做

生产者-消费者问题(互斥、同步综合问题)

系统中有一组生产者进程和一组消费者进程,生产者进程每次生产一个产品放入缓冲区,消费者进程每次从缓冲区中取出一个产品并使用

  • 生产者、消费者共享一个初始为空、大小为n的缓冲区
  • 只有缓冲区没满时,生产者才能把产品放入缓冲区,否则必须等待(进程同步:缓冲区没满 -> 生产者生产)
  • 只有缓冲区不空时,消费者才能从中取出产品,否则必须等待(进程同步:缓冲区没空 -> 消费者消费)
  • 缓冲区是临界资源,各进程必须互斥访问(如果两个生产者进程并发运行,同时往缓冲区的同一块区域放入数据,会出现问题)(互斥关系)

❗️互斥信号量初值一般为1,同步信号量的初始值要看对应资源的初始值是多少

能否改变相邻P、V操作的顺序?

“生产一个产品”、“使用产品”这两句话逻辑上是可以放到PV操作之间,但是这样会增加临界区的代码,会导致进程的并发度降低,对系统的效能产生影响,造成不便

多生产者-多消费者问题

  • 桌子上有一只盘子,每次只能向其中放入一个水果。(大小为1,初始为空的缓冲区)
  • 爸爸专向盘子中放苹果,妈妈专向盘子中放橘子,儿子专等着吃盘子中的橘子,女儿专等着吃盘子中的苹果。
  • 只有盘子空时,爸爸或妈妈才可向盘子中放一个水果。仅当盘子中有自己需要的水果时,儿子或女儿可以从盘子中取出水果。用PV操作实现上述过程

互斥关系:对缓冲区(盘子)的访问要互斥的进行

同步关系(一前一后):

  1. 盘子中有苹果,女儿才能取苹果
  2. 盘子中有橘子,儿子才能取橘子
  3. 只有盘子为空时,父亲或母亲才能放入水果(“盘子为空”这个事件可以由儿子或女儿触发,事件发生后才允许父亲或母亲放水果)

问题:可不可以不用互斥信号量

分析:刚开始,儿子、女儿进程即使上处理机运行也会被阻塞,如果刚开始是父亲进程先上处理机运行,则父亲P(plate),可以访问盘子 -> 母亲P(plate),阻塞等待盘子 -> 父亲放入苹果V(apple),女儿进程被唤醒,其他进程即使运行也都会阻塞,暂时不能访问临界资源(盘子) -> 女儿P(apple),访问盘子,V(plate),等待盘子的母亲进程被唤醒 -> 母亲进程访问盘子(其他进程暂时无法进入临界区) -> …

结论:即使不设置专门的互斥变量mutex,也不会出现多个进程同时访问盘子的现象

原因:本题中的缓冲区大小为1,在任何时刻,apple、orange、plate三个同步信号量中最多只有一个是1,因此在任何时刻,最多只有一个进程的P操作不会被阻塞,并顺利的进入临界区

如果盘子的容量(缓冲区)设为2,可能出现两个进程同时访问缓冲区的情况,有可能导致两个进程写入缓冲区的数据相互覆盖的情况

❗️因此,如果缓冲区大小大于1,就必须专门设置一个互斥信号量mutex来保证互斥访问缓冲区

吸烟者问题(可生产多种产品的单生产者-多消费者)

假设一个系统有三个抽烟者进程和一个供应者进程。每个抽烟者不停的卷烟并抽掉它,但是要卷起并抽掉一支烟,抽烟者需要有三种材料:烟草、纸和胶水。三个抽烟者中,第一个拥有烟草、第二个拥有纸、第三个拥有胶水。供应者进程无限的提供三种材料,供应者每次将两种材料放桌子上,拥有剩下那种材料的抽烟者卷一根烟并抽掉它,并给供应者进程一个信号告诉它完成了,供应者就会放另外两种材料在桌上,这个过程一直重复(让三个抽烟者轮流的抽烟)

互斥关系:桌子可以抽象为容量为1的缓冲区,要互斥访问(这个容量为1表示组合)

  • 组合一:纸+胶水
  • 组合二:烟草+胶水
  • 组合三:烟草+纸

同步关系(从事件的角度来分析):

  • 桌上有组合一,第一个抽烟者取走东西
  • 桌上有组合二,第二个抽烟者取走东西
  • 桌上有组合三,第三个抽烟者取走东西
  • 发出完成信号,供应者将下一个组合放到桌上

读者-写者问题(复杂的互斥问题)

有读者和写者两组并发进程,共享一个文件,当两个或两个以上的读进程同时访问共享数据时不会产生副作用,但若某个写进程和其他进程(读进程或写进程)同时访问共享数据时则可能导致数据不一致的错误,因此要求:

  1. 允许多个读者可以同时对文件执行读操作(与消费者进程不同,读者进程在读数据后并不会将数据清空,并不会改变数据。因此多个读者进程可同时访问共享数据)
  2. 只允许一个写者往文件中写信息
  3. 任一写者在完成写操作之前不允许其他读者或写者工作(两个写进程同时共享数据,可能导致数据错误覆盖的问题)(读进程与写进程同时共享数据,可能导致读出的数据不一致的问题)
  4. 写者执行写操作前,应让已有的读者和写者全部退出

两类进程:写进程、读进程

互斥关系:写进程-写进程、写进程-读进程(读进程-读进程不存在互斥问题)

思考:若两个读进程并发执行,则count=0时两个进程也许都能满足if条件,都会执行P(rw),从而使第二个读进程阻塞的情况

如何解决:出现上述问题的原因在于对count变量的检查和赋值无法一气呵成,因此可以设置另一个互斥信号量来保证各读者进程对count的访问是互斥的

潜在的问题:只要有读进程还在读,写进程就要一直阻塞等待,可能“饿死”。因此这种算法中,读进程是优先的

结论;在这种算法中,连续进入的多个读者可以同时读文件;写者和其他进程不能同时访问文件;写者不会饥饿,但也并不是真正的“写优先”,而是相对公平的先来先服务原则。有的书上把这种算法称为“读写公平法”

哲学家进餐问题(解决进程死锁)

一张圆桌上坐着5名哲学家,每两个哲学家之间的桌上摆一根筷子,桌子的中间是一碗米饭。哲学家们倾注毕生的精力用于思考和进餐,哲学家在思考时,并不影响其他人。只有当哲学家饥饿时,才试图拿起左、右两根筷子(一根一根的拿起)。如果筷子已在他人手上,则需等待。饥饿的哲学家只有同时拿起两根筷子才可以开始进餐,当进餐完毕后,放下筷子继续思考

系统中有5个哲学家进程,5位哲学家与左右邻居对其中间筷子的访问是互斥关系

这个问题中只有互斥关系,但与之前遇到的问题不同的是,每个哲学家进程需要同时持有两个临界资源才能开始吃饭。如何避免临界资源分配不当造成的死锁现象,是哲学家问题的精髓

定义互斥信号量数组chopstick[5] = {1,1,1,1,1},用于实现对5个筷子的互斥访问,并对哲学家按0~4编号,哲学家i左边的筷子编号为i,右边的筷子编号为(i+1) % 5

如果5个哲学家并发的拿起了自己左手边的筷子,每位哲学家循环等待右边的人放下筷子(阻塞),发生“死锁”

解决方法:

  1. 可以对哲学家进程施加一些限制条件,比如最多只允许四个哲学家同时进餐。这样可以保证至少有一个哲学家是可以拿到左右两只筷子的

  2. 要求奇数号哲学家先拿左边的筷子,然后再拿右边的筷子,而偶数号哲学家刚好相反。用这种方法可以保证如果相邻的两个奇偶号哲学家都想吃饭,那么只会有其中一个可以拿起第一支筷子,另一个会直接阻塞。这就避免了占有一支后再等待另一只的情况

  3. 仅当一个哲学家左右两支筷子都可用时才允许他抓起筷子

画图可知,进程分别调度哲学家0、1、2时可发现,哲学家0可正常吃饭,哲学家1被阻塞,哲学家2虽然左右都有筷子,但是他也被阻塞

进程分别调度0、4时,哲学家0可正常吃饭,哲学家4右边的筷子不可用,但是4仍然可以拿起左边的筷子,因此这种方法并不能保证只有两边的筷子都可用时,才允许哲学家拿起筷子