生产者-消费者问题

作为操作系统最精华的部分,生产者消费者问题无疑是经典问题中的经典问题。

今天终于有空能好好研究一下这类问题了,不对之处还望大家指正。

首先,讲解经典的生产者消费者问题。

问题描述:一组生产者进程和消费者进程共享一个初始为空,大小为n的缓冲区。只有当缓冲区没满的时候,生产者才能将消息放进去。同理,只有当缓冲区不空的时候,消费者才能从中取消息,否则必须等待。由于缓冲区是临界资源,它只允许一个生产者放入消息,也只允许一个消费者拿出消息。这里我再解释一下,意思是,同一个时刻只能是一个生产者或者一个消费者操作缓冲区,禁止一下情况:多个生产者或者多个消费者操作缓冲区,同样,一个生产者和一个消费者同时操作也是禁止的。

分析:首先,生产者之间,消费者之间是互斥的关系,同时生产者消费者之间又是协同的关系,属于进程同步。

其次,设置信号量,我们知道信号量个数等于资源数,我们用empty=n表示缓冲区空的缓冲区数目,用full=0表示缓冲区满的缓冲区数目。同时,还要一个信号量mutex来实现,诸进程对缓冲区的互斥访问。


int in = 0,out = 0;  //缓冲区 生产和消费初始的指针
item buffer[n];    //表示n个缓冲区
semaphore mutex = 1;  //临界区互斥信号量
semaphore empty = n;  //空闲缓冲区为n
semaphore full = 0;   //缓冲区初始化为空

void producer(){    //生产者进程
    do{
        producer an item nextp   //生产数据
        ...
        wait(empty);          //获取空缓冲区单元
        wait(mutex);         //进入临界区
        buffer[in] = nextp;
        in = (in+1)%n;
        signal(mutex);      //离开临界区,释放互斥信号量
        signal(empty);      //满缓冲区数加1
    }
    while(TRUE);
}

void consumer(){       //消费者进程
    do{
        wait(full);    //获取满缓冲区单元
        wait(mutex);   //进入临界区
        nextc = buffer[out];
        out = (out+1)%n;
        signal(mutex);    //离开临界区,释放互斥信号量
        signal(empty);     //空缓冲区数加1
        consumer the item in nextc
        ...
    }while(TRUE);
}

void main(){
    cobegin       //并发执行生产者进程和消费者进程
        producer(); consumer();
    coend
}

再补充一下,wait操作和signal操作

void wait(semaphore *S){
    S->value --;
    if(S->value < 0)
        block(S->list);  //value的值为负数,表示当前资源数最大为0,即没有可用的资源了
}

void signal(semaphore *S){
    S->value ++;
    if(S->value <= 0)
        wakeup(S->list);  //value的值为0,没有加1之前是-1表示当前还有一个进程在阻塞
}

//value的绝对值表示当前进程阻塞数目

当然这只是最基本的情况,生产者消费者问题有很多变种,要是这么简单我也不会发这篇博文了。

变种1: 调整生产者或者消费者的wait操作顺序,是否对结果有影响?

这里我把生产者消费者混在一起,是因为它们之间是类似的情况。

结果是有影响。因为顺序一旦调整(假设调整的是生产者的wait顺序),当缓冲区满的时候,下一个时刻如果还是生产者抢到CPU的资源,首先执行wait(mutex)操作,将信号量封锁,然后执行wait(empty)操作,发现没有空缓冲区了,生产者进程就进入阻塞状态,下一个时刻若是消费者抢到CPU资源,发现mutex值为0,进入阻塞。这样一来生产者消费者都期待对方唤醒自己,结果陷入了无休止的等待。消费者调整顺序与此情况类似。

变种2:调整生产者或者消费者的signal操作顺序,是否对结果有影响?

结果是没有影响。因为唤醒某个进程是不会引起阻塞的。

变种3: 单生产者,单消费者,多缓冲区时,怎样设置信号量?

这里因为生产者消费者只有一个,不存在生产者之间和消费者之间的互斥,只要设置两个信号量empty=n,full=0

因为这里多缓冲区这个条件是没有变化的。有人说了,不设置mutex,会导致消费者生产者同时操作缓冲区。这到底要不要紧呢?

答案是不要紧,因为in,out指针表明了,生产者和消费者同时操作缓冲区内一前一后指针所指向的数,也就是说,生产者生产的数据放在队尾,消费者消费的数据是队头的数据,怎么会冲突呢?

变种4: 多生产者多消费,单缓冲区

很多人刚开始认为此类型只要设置一个mutex就可以了,答案是不可以的。为什么?因为如果生产者生产了一个数据,消费者没有消费,紧接着生产者又生产了,因为只有一个缓冲区,所以数据就被覆盖了。正确的方案是 设置两个信号量empty = 1,full = 0可以保证生产者生产一个数据,然后只有消费者才能消费,这样循环下去。其实这种情况和输入打印有点类似。

变种5: 单生产者单消费者,单缓冲区

此种情况 和 变种4是一致的。 对于单缓冲区,也有空 和 满 两种情况。也必须按顺序执行,不能有被覆盖的数据。

变种6:允许生产者写时,消费者可读

这种情况下就不能设置一个mutex了,因为生产者和消费者可以同时访问缓冲区,解决方案是 给生产者消费者分别设置mutexA,mutexB,使它们各自互斥,但是生产者和消费者之间不互斥

变种7:缓冲区无限大

这种情况下,就不要设置empty信号量了,只用一个full表示缓冲区满的个数,这个个数表示的是缓冲区内数据的个数

变种8: 每个消息都要每个消费者消费一次

这种情况下,我举一个例子来说明。

假设有生产者A,消费者B,C,D,它们之间共享一个缓冲区,A生产之后,消费者B,C,D都要消费一次,然后A才能再生产。

这里,我设置了三个互斥量mutexB,mutexC,mutexD 分别于A互斥,初值都为1,再设置empty=1,full=0

生产者进程里执行三个wait(mutexA),wait(mutexB),wait(mutexC)之后,确保三个消费者都消费一次之后,再进行生产操作。


你可能感兴趣的:(操作系统)