文章目录
- 2.3_6 生产者消费者问题
- (一)问题描述
- (二)问题分析
- (三)如何实现
- (四)思考:能否改变相邻P、V操作的顺序?
- 总结
2.3_6 生产者消费者问题
(一)问题描述
系统中有一组生产者进程和一组消费者进程,生产者进程每次生产一个产品放入缓冲区,消费者进程每次从缓冲区中取出一个产品并使用。(注:这里的“产品”理解为某种数据)
生产者、消费者共享一个初始为空、大小为n的缓冲区。
只有缓冲区没满时,生产者才能把产品放入缓冲区,否则必须等待。
分析1:
缓冲区没满 ---> 生产者生产
。这是一对一前一后的同步关系。
因为缓冲区大小为5,所以生产者进程最多可以向缓冲区内放入5个产品。
如果此时缓冲区已经满了,但是生产者进程还想尝试往里面写数据,那么生产者进程必须阻塞等待,只有缓冲区有空位的时候,生产者进程才能继续写数据。
当一个消费者进程从缓冲区取走数据之后,如果此时有生产者进程是处于阻塞状态的,那么该生产者进程就要把生产者进程给唤醒,让它重新回到就绪态。
注意:生产者进程只是回到了就绪态,但并不意味着生产者进程会立即往缓冲区里写数据。接下来有可能是消费者进程继续从缓冲区中读数据。
只有缓冲区非空的时候,消费者进程才有可能从缓冲区中取走数据,否则消费者进程就必须阻塞等待。
分析2:
缓冲区非空 ---> 消费者消费
。这是一对一前一后的同步关系。
缓冲区是临界资源,各进程必须互斥地访问。
分析3:互斥关系。
假设有两个生产者进程同时往缓冲区中写数据,并且写入的位置是同一片区域(如上图棕色部分)。这样的话,后写的就会把先写的数据覆盖掉。因此,如果各个进程同时访问缓冲区的话,就有可能出现一系列问题。
(二)问题分析
注意,对此类问题分析的步骤:
1.关系分析。找出题目中描述的各个进程,分析它们之间的同步、互斥关系。
2.整理思路。根据各进程的操作流程确定P、V操作的大致顺序。
3.设置信号量。并根据题目条件确定信号量初值。
注意:互斥信号量初值一般为1,同步信号量的初始值要看对应资源的初始值是多少。
对上例的分析,总结如下:
1.生产者、消费者共享一个初始为空、大小为n的缓冲区。
2.只有缓冲区没满时,生产者才能把产品放入缓冲区,否则必须等待。
同步关系1:缓冲区没满 —> 生产者生产。
3.只有缓冲区不空时,消费者才能从中取出产品,否则必须等待。
同步关系2:缓冲区没空 —> 消费者消费。
既然有两对一前一后的同步关系,那么我们就要分别对它们各设置一个同步信号量。
并且不要忘了——在“前操作”执行完之后,要对这个信号量执行一个V()操作;在“后操作”开始执行之前,要对这个信号量执行一个P()操作。
4.缓冲区是临界资源,各进程必须互斥地访问。
利用信号量实现互斥,很简单,设置一个信号量mutex,令其初值为1。然后在临界区的前面和后面分别对互斥信号量执行P、V操作就可以了。
5.对于同步信号量full
的理解。full是消费者进程要从缓冲区中读取产品的产品数量。而缓冲区刚开始是空的,每当生产者进程生产一次,就会释放一个full,因此full初始值应该是0。
6.对于同步信号量empty
的理解。生产者每生产一个产品,就需要消耗一个空闲的缓冲区。因此empty这个信号量所对应的资源,就是“空闲的缓冲区”这种资源。它的数量就是空闲缓冲区的数量。每当一个消费者进程消费一次,就会释放一个empty,因此empty的初始值应该是n。
(三)如何实现
1.生产者、消费者共享一个初始为空、大小为n的缓冲区。
2.只有缓冲区没满时,生产者才能把产品放入缓冲区,否则必须等待。
3.只有缓冲区不空时,消费者才能从中取出产品,否则必须等待。
4.缓冲区是临界资源,各进程必须互斥地访问。
先把各个进程做的事情写出来。之后再考虑在其前、后怎样插入P、V操作的事情。
如上图所示,此时我们就实现了两对同步关系。
此外,由于缓冲区是互斥的临界资源,因此在访问缓冲区的代码前后分别要对mutex这个互斥信号量执行P、V操作,用于实现对缓冲区的互斥访问。
注意:实现互斥,是在同一进程中进行的一对PV操作。
而实现两个进程的同步关系,是在其中一个进程中执行P,另一个进程中执行V。
注:这种P、V操作的放置位置,是可以根据刚才我们分析好的同步关系,以及画好的前驱图,从而轻松想到的。
(四)思考:能否改变相邻P、V操作的顺序?
问题1
例如producer()
进程中,刚才我们是先对同步信号量empty执行P操作,再对互斥信号量mutex执行P操作。那么现在,如果我们把这两个P操作的位置颠倒一下,会不会出问题?
分析
若这样摆放P操作的话,那么,如果此时缓冲区内已经放满产品,即empty=0,full=n。
则生产者进程执行①使mutex变为0,再执行②。而又由于已经没有空闲缓冲区,因此生产者被阻塞。
由于生产者阻塞,因此切换到消费者进程。消费者进程执行③,但又由于mutex=0,即生产者还没对临界资源“解锁”,因此消费者也被阻塞。
这就造成了生产者等待消费者释放空闲缓冲区,而消费者又等待生产者释放临界区的情况。生产者和消费者循环等待被对方唤醒,出现“死锁”。
同样地,若缓冲区中没有产品,即full=0,empty=n。按③④①的顺序执行就会发生死锁。
因此,实现互斥的P操作一定要在实现同步的P操作之后。
V操作不会导致进程阻塞,因此两个V操作顺序可以交换。
问题2
生产者的生产一个产品
操作,以及消费者使用产品
操作,能否也放到PV操作中间?
分析
单从代码逻辑来看,将它们也放到PV操作中间,是不会出问题的。
但是,如果把它们也放到PV操作中间,即把它们也当成了临界资源来处理,会导致临界区部分的代码变得更长,也就是说一个进程对临界区上锁的时间会增长。这样一来,肯定不利于各个进程去交替地使用临界区资源。所以,我们要让临界区的代码尽可能的短,因此,逻辑上来看,把它们放进去是没有问题的,但实际上来讲,会对系统的效能产生影响。
因此,并不建议把这两个操作放在PV操作中间。
总结
PV 操作题目的解题思路:
1.关系分析。找出题目中描述的各个进程,分析它们之间的同步、互斥关系。
2.整理思路。根据各进程的操作流程确定P、V操作的大致顺序。
3.设置信号量。设置需要的信号量,并根据题目条件确定信号量初值。(互斥信号量初值一般为1,同步信号量的初始值要看对应资源的初始值是多少)
生产者消费者问题
生产者消费者问题是一个互斥、同步的综合问题。对于初学者来说最难的是发现题目中隐含的两对同步关系。有时候是消费者需要等待生产者生产,有时候是生产者要等待消费者消费,这是两个不同的“一前一后问题”,因此也需要设置两个同步信号量。
易错点
实现互斥和实现同步的两个P操作的先后顺序。(死锁问题)