1.问题描述

系统中有一组生产者进程和一组消费者进程,生产者进程每次生产一个产品放入缓冲区,消费者进程每次从缓冲区中去除一个产品并使用。(这里的“产品”理解为某种数据)

生产者、消费者共享一个初始为空、大小为 n 的缓冲区。

只有缓冲区没满时,生产者才能把产品放入缓冲区。

只有缓冲区不空时,消费者才能从中取走产品,否则必须等待。

生产者进程每次生产一个产品,消费者进程每次取走一个产品。当缓冲区为满时,只能阻塞生产者进程,切换为消费者进程取走产品。只要缓冲区中又大于或等于 1 个可用的空间时就可以唤醒生产者进程继续生产。

OC3t2t.png

当缓冲区为空,消费者进程就没有可以被取走的产品了,只能阻塞消费者进程,等待产品被放入缓冲区。

OC8AL8.png

缓冲区属于临界资源,各个进程必须互斥地访问。如果不属于临界资源,则各生产者进程并发地执行时由于无法控制执行进度,有可能导致两个进程同时访问缓冲区的同一块区域导致数据冲突。

如何用信号量机制(P、V 操作)实现生产者、消费者进程的这些功能?

信号量机制可以实现互斥、同步、对一类系统资源的申请和释放。

互斥:设置初始值为 1 的互斥信号量。

同步:设置初始值为 0 的同步信号量(实现“一前一后”)

申请和释放资源:设置一个信号量,初始值为资源的数量(本质上也是属于“同步问题”,若无空闲资源,则申请自愿的进程需要等待别的进程释放资源后才能据需执行下去)

分析:

1.关系分析:找出题目中描述的各个进程,分析它们之间的同步关系。

  • 只有缓冲区没满时,生产者才能把产品放入缓冲区。这个属于同步关系。缓冲区没满时,生产者要等待消费者取走产品。

  • 只有缓冲区不空时,消费者才能从中取走产品,否则必须等待。这个也属于同步关系。缓冲区为空时(即没有产品时),消费者要等待生产者生产商品。

  • 只有缓冲区不空时,消费者才能从中取走产品,否则必须等待。这个属于互斥关系。

    2.整理思路,根据各进程的操作流程确定 P、V 操作的大致顺序。

生产者每次要消耗(P)一个空闲缓冲区,并生产(V)一个产品。消费者者每次要消耗(P)一个产品,并释放一个空闲的缓冲区(V)。往缓冲区放入,取走产品需要互斥。

3.设置信号量。设置需要的信号量,并根据题目题目条件确定信号量初值(互斥信号量初值一般为 1,同步信号量的初始值要看对应资源的初始值是多少)

semaphore mutex = 1;  //互斥信号量,实现进程互斥访问。
semaphore empty = n;  //同步信号量,表示空闲缓冲区的数量。
semaphore full = 0;   //同步信号量,表示产品的数量,也即非空缓冲区的数量。

2.如何实现

根据上面设置的信号量,实现响应代码:

//生产者进程
producer(){
    while(1){
        生产一个产品;
        P(empty);   //消耗一个空闲缓冲区
        P(mutex);   //实现进程互斥
        把产品放入缓冲区;
        V(mutex);
        V(full);    //增加一个产品
    }
}


//消费者进程
consumer(){
    while(1){
        P(full);   //消耗一个产品(非空缓冲区),此P操作用于实现进程同步
        P(mutex);  //此P操作用于实现进程互斥
        从缓冲区取走一个产品;
        V(mutex);
        V(empty);   //增加一个空闲缓冲区
    }
}


/*
P(mutex);
把产品放入缓冲区;
V(mutex);
上面的代码实现了进程互斥,实现互斥是在同一进程中进程一对PV操作

生产者进程的V(full)和消费者进程的P(full)用于实现两个进程的同步关系。即在其中一个一个进程中执行P,另一个进程中执行V
*/

3.思考:能否改变相邻 P,V 操作的顺序?

OCUYTK.png

生产者生产产品和消费者消费产品从代码上看是放在 PV 操作之外。能不能放到 PV 操作之内呢?从逻辑上来看是没问题,可以让消费者从缓冲区中取出一个产品后就立即使用这个产品。但会导致临界区代码量变大,消费者进程在访问临界区的过程中就需要花费更多的时间,如果此时有别的进程也需要访问临界资源则消费者进程会被阻塞,会导致进程的并发度降低,尽量不要放入 PV 操作之中。

4.知识回顾

OCaFhD.png