Bootstrap

信号的同步互斥——五个经典问题

单生产者和单消费者问题

描述:
有两个进程:一组生产者进程和一组消费者进程共享一个初始为空、固定大小为n的缓存(缓冲区)。生产者的工作是制造一段数据,只有缓冲区没满时,生产者才能把消息放入到缓冲区,否则必须等待,如此反复; 同时,只有缓冲区不空时,消费者才能从中取出消息,一次消费一段数据(即将其从缓存中移出),否则必须等待。由于缓冲区是临界资源,它只允许一个生产者放入消息,或者一个消费者从中取出消息。

问题的核心是:

  1. 要保证不让生产者在缓存还是满的时候仍然要向内写数据;
  2. 不让消费者试图从空的缓存中取出数据。

生产者和消费者对缓冲区互斥访问是互斥关系,同时生产者和消费者又是一个相互协作的关系,只有生产者生产之后,消费者才能消费,他们也是同步关系。

解决思路:对于生产者,如果缓存是满的就去睡觉。消费者从缓存中取走数据后就叫醒生产者,让它再次将缓存填满。若消费者发现缓存是空的,就去睡觉了。下一轮中生产者将数据写入后就叫醒消费者。
不完善的解决方案会造成“死锁”,即两个进程都在“睡觉”等着对方来“唤醒”。

只有生产者和消费者两个进程,正好是这两个进程存在着互斥关系和同步关系。那么需要解决的是互斥和同步PV操作的位置。使用“进程间通信”,“信号标”semaphore就可以解决唤醒的问题:

方法
我们使用了两个信号标:full 和 empty 。信号量mutex作为互斥信号量,它用于控制互斥访问缓冲池,互斥信号量初值为 1;信号量 full 用于记录当前缓冲池中“满”缓冲区数,初值为0。信号量 empty 用于记录当前缓冲池中“空”缓冲区数,初值为n。新的数据添加到缓存中后,full 在增加,而 empty 则减少。如果生产者试图在 empty 为0时减少其值,生产者就会被“催眠”。下一轮中有数据被消费掉时,empty就会增加,生产者就会被“唤醒”。

伪代码

semaphore mutex=1; //临界区互斥信号量
semaphore empty=n;  //空闲缓冲区
semaphore full=0;  //缓冲区初始化为空
producer ()//生产者进程 
{
    while(1)
    {
        produce an item in nextp;  //生产数据
        P(empty);  //获取空缓冲区单元
        P(mutex);  //进入临界区.
        add nextp to buffer;  //将数据放入缓冲区
        V(mutex);  //离开临界区,释放互斥信号量
        V(full);  //满缓冲区数加1
    }
}

consumer ()//消费者进程
{
    while(1)
    {
        P(full);  //获取满缓冲区单元
        P(mutex);  // 进入临界区
        remove an item from buffer;  //从缓冲区中取出数据
        V (mutex);  //离开临界区,释放互斥信号量
        V (empty) ;  //空缓冲区数加1
        consume the item;  //消费数据
    }
}

多生产者和多消费者问题

问题描述
桌子上有一只盘子,每次只能向其中放入一个水果。爸爸专向盘子中放苹果,妈妈专向盘子中放橘子,儿子专等吃盘子中的橘子,女儿专等吃盘子中的苹果。只有盘子为空时,爸爸或妈妈就可向盘子中放一个水果;仅当盘子中有自己需要的水果时,儿子或女儿可以从盘子中取出。

问题分析
(1)关系分析。这里的关系稍复杂一些,首先由每次只能向其中放入一只水果可知爸爸和妈妈是互斥关系。爸爸和女儿、妈妈和儿子是同步关系,而且这两对进程必须连起来,儿子和女儿之间没有互斥和同步关系,因为他们是选择条件执行,不可能并发。

(2)整理思路。这里有4个进程,实际上可以抽象为两个生产者和两个消费者被连接到大小为1的缓冲区上。

进程之间的关系:

在这里插入图片描述
(3)信号量设置。首先设置信号量plate为互斥信号量,表示是否允许向盘子放入水果,初值为1,表示允许放入,且只允许放入一个。信号量 apple表示盘子中是否有苹果,初值为0,表示盘子为空,不许取,若apple=l可以取。信号量orange表示盘子中是否有橘子,初值为0,表示盘子为空,不许取,若orange=l可以取。

伪代码

semaphore plate=l, apple=0, orange=0;
dad() {  //父亲进程
    while (1) {
        prepare an apple;
        P(plate) ;  //互斥向盘中取、放水果
        put the apple on the plate;  //向盘中放苹果
        V(apple);  //允许取苹果
    }
}
 
mom() {  // 母亲进程
    while(1) {
        prepare an orange;
        P(plate);  //互斥向盘中取、放水果
        put the orange on the plate;  //向盘中放橘子
        V(orange); //允许取橘子
    }
}
 
son(){  //儿子进程
    while(1){
        P(orange) ;  //互斥向盘中取橘子
        take an orange from the plate;
        V(plate);  //允许向盘中取、放水果
        eat the orange;
    }
}
 
daughter () {  //女儿进程
    while(1) {
        P(apple);  // 互斥向盘中取苹果
        take an apple from the plate;
        V(plate);  //运行向盘中取、放水果
        eat the apple;
    }
   }

吸烟者问题

问题描述
某系统有三个吸烟者进程和一个经销商进程:

  • 每个吸烟者连续不断做烟卷并抽他做好的烟卷,做一支烟卷需要烟草、纸、火柴三种原料,这3个吸烟者分别掌握有烟草、纸和火柴;
  • 经销商源源不断地提供上述三种原料,但他只将其中的两种原料放在桌上,具有另一种原料的吸烟者就可做烟卷并抽烟,且在做完后给经销商发信号,然后经销商再拿出两种原料放在桌上,如此反复。
  • 试设计一个同步算法来描述他们的活动。

分析
吸烟者问题是经典的进程同步问题,其特点在于信号量的设置,本题的关键问题是判断有几个临界资源。烟草、纸和火柴三种原料并不能简单地看成是三种临界资源,因为它们并不是以单独的形式被三个吸烟者进程所竞争,而是以固定的组合被三个进程所申请因此可以考虑如下设置:

  • 三个信号量t1、t2和t3分别代表三种原料组合,即t1表示烟草和纸的组合信号量,t2表示烟草和火柴的组合信号量,t3表示纸和火柴的组合信号量,初值均为0。
  • 经销商一次只能提供一种组合,可以看作是放一个产品的缓冲区,资源量为一个组合,于是我们设置缓冲区的资源信号量为s,初值为1。由于该资源量为1,故可以同时作为互斥量来使用,可以省略对缓冲区操作的互斥信号量。
    在这里插入图片描述
    伪代码
int random; //存储随机数
semaphore offer1=0; //定义信号量对应烟草和纸组合的资源
semaphore offer2=0; //定义信号量对应烟草和胶水组合的资源
semaphore offer3=0; //定义信号量对应纸和胶水组合的资源
semaphore finish=0; //定义信号量表示抽烟是否完成
 
//供应者
while(1){
    random = 任意一个整数随机数;
    random=random% 3;
    if(random==0)
        V(offerl) ; //提供烟草和纸
    else if(random==l) 
        V(offer2);  //提供烟草和胶水
    else
        V(offer3)  //提供纸和胶水
    // 任意两种材料放在桌子上;
    P(finish);
}
 
//拥有烟草者
while(1){
    P (offer3);
    // 拿纸和胶水,卷成烟,抽掉;
    V(fi
;