经典进程同步问题——生产者消费者(多生产者多消费者)

2023-05-16

生产者-消费者问题

问题描述:一组生产者进程和一组消费者进程共享一个初始为空、大小为n的缓冲区,只有缓冲区没有满的时候,生产者才能把消息放入缓冲区,否则得等待缓冲区空闲出来;只有缓冲区不空的时,消费者才能从缓冲区取出消息,否则必须等待。由于缓冲区是临界资源,它只允许一个生产者放入消息,或一个消费者取出消息。

问题分析

  1. 关系分析。生产者消费者是互斥关系,同时它们也是相互协作的关系,只有生产者生产了,消费者才能消费,它们也是同步关系。
  2. 整理思路。只有生产者和消费者两个进程,正好这两个是互斥和同步的关系。那么需要解决的是互斥和同步的PV操作的位置。
  3. 信号量设置。信号量mutex作为互斥信号量,用于控制互斥访问缓冲区,初始值为1;信号量full用于记录当前缓冲区的“满”,初值为0。信号量empty用于记录当前缓冲区“空”,初值为n。(这里会判断full和n的大小然后做出操作)

在之前信号量机制一文已经出现过了互斥和同步的例子。生产者消费者与之前思想一样。描述如下:

  1. semaphore mutex = 1;//临界区互斥信号量  
  2. semaphore empty = n;//空闲缓冲区  
  3. semaphore full = 0;//缓冲区初始化为空  
  4. producer(){ //生产者  
  5.   while(1){  
  6.     produce an item in nextp; //生产数据  
  7.     P(empty); (要用什么P一下) //获取空缓冲区单元  
  8.     P(mutex);                 //互斥,进入临界区  
  9.     add nextp to buffer;      //将数据放入缓冲区  
  10.     V(mutex);                 //离开临界区,释放互斥信号量  
  11.     V(full);                  //满缓冲区数加1,相当于放入缓冲区了,让缓冲区的数加1  
  12.   }  
  13. }  
  14. consumer(){  
  15.   while(1){  
  16.     P(full);//获取满缓冲区单元,如果没有数据就等待  
  17.     P(mutex);//进入缓冲区  
  18.     remove an item from buffer;//从缓冲区取出数据  
  19.     V(mutex);//离开临界区,释放互斥信号量  
  20.     V(empty);//空缓冲区数加1  
  21.     consume the item;//消费数据  
  22.   }  
  23. }  

生产者消费者要注意对缓冲区大小为n的处理,当缓冲区有空时,便可以对empty变量执行P操作,一旦取走一个产品便要执行V操作用来释放空闲区。可以用下面的关系来更好的了解这个生产者消费者例子。

有产品(缓冲区没空)V操作——full——P操作——>消费者消费

生产者生产<——P操作——empty——V操作——缓冲区没满

举个例子,缓冲区的大小n为3,那么一开始它里面是没有数据的,consumer会先执行P(full)操作,这时候它会先让full减1,然后判断full是不是小于0,由于这时full是-1,那么将full加入等待队列并阻塞(加粗这段,这是P操作,P操作在信号量机制一文)。所以接下来执行的是producer(),先生产数据,对empty进行P操作,empty的初始值就是3也就是n,那么它肯定是不会阻塞的,接下来进入临界区,把数据放入缓冲区,离开临界区后唤醒了full。这时consumer就可以继续执行下去消费缓冲区的数据了。

注:对empty和full的P操作一定要在mutex的P操作前,比如缓冲区已经放满了,消费者没有取产品,即empty为0,当下次仍然是生产者进程的时,它先执行P(mutex),再执行P(empty)时会被阻塞,希望消费者取出产品后将其唤醒。轮到消费者运行时,它先执行P(mutex),由于你之前已经进行了这个操作,所以阻塞,那么就永远不能唤醒。

综上所得实现互斥的操作一定要在实现同步的P操作之后

这里顺带提一下较为复杂的生产者-消费者问题。在王道的操作系统书上有个爸妈给儿子女儿吃水果的问题。问题大概内容如下:桌子上有个盘子(类似于一个临界资源,所有的进程只能互斥访问),每次只能向其中放入一个水果(临界资源=1)。爸爸只往盘子中放苹果,妈妈只放橘子,儿子专门等盘子中的橘子,女儿则等苹果。只有盘子空时,爸妈才可向盘子放一个水果;当盘子中有自己需要的水果时,儿子或女儿才能取。

下面针对这个问题做出分析。

互斥关系:盘子的访问要互斥的进行。

同步关系:1.父亲放,女儿取  2.母亲放,儿子取  3.只有盘子为空,父亲或母亲才能放入水果。

信号量设置:首先信号量plate设置互斥信号量,表示是否允许向盘子放入水果,初值为1表示允许放入,且只允许放入一个。信号量apple表示盘子中是否有苹果,初值为0表示盘子为空,不许取,apple=1表示可以取。信号量orange表示盘子中是否有橘子,初值为0表示盘子为空,不许取,orange=1表示可以取。(在这里的缓冲区大小为1,在任何时候,apple、orange、plate三个同步信号量中最多只有一个是1。因此在任何时刻,最多只有一个进程的P操作不会被阻塞,并顺利进入临界区。如果缓冲区大于1,必须设置一个互斥信号量,不然数据会被覆盖,具体问题具体分析)。

代码描述如下:

  1. semaphore plate=1,apple=0,orange=0;  
  2. dad(){//父亲进程  
  3.  while(1){  
  4.    prepare an apple;  
  5.    P(plate); //互斥向盘中取、放水果  
  6.    put the apple on the plate;//向盘中放水果  
  7.    V(apple);//允许取苹果  
  8.  }  
  9. }  
  10. mon(){//母亲进程  
  11.  while(1){  
  12.    prepare an orange;  
  13.    P(plate); //互斥向盘中取、放水果  
  14.    put the orange on the plate;//向盘中放水果  
  15.    V(orange);//允许取橘子  
  16.  }  
  17. }  
  18. son(){  
  19.  while(1){  
  20.    P(orange);//互斥向盘中取橘子  
  21.    take an orange from the plate;  
  22.    V(plate);//允许向盘中取水果,放水果  
  23.    eat the orange;  
  24.  }  
  25. }  
  26. daughter(){  
  27.  while(1){  
  28.    P(apple);//互斥向盘中取苹果  
  29.    take an apple from the plate;  
  30.    V(plate);//允许向盘中取水果,放水果  
  31.    eat the apple;  
  32.  }  
  33. }  

其实就是生活中的想法一样,父亲放了,儿子就可以取,母亲放了,女儿就可以取。如果女儿想先取,那么发现不是苹果是橘子,那么等儿子取完,母亲放好苹果可以再取。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

经典进程同步问题——生产者消费者(多生产者多消费者) 的相关文章

随机推荐