P/V操作
def: P/V操作:由P操作原语和V操作原语组成,其意义是在一个信号量的S上定义了两个操作,该变量的值只能由P操作和V操作进行修改。并且这两个操作均属于原子操作,不可被中断。
//P原语
P(S)
{
S--; //信号量减一
if S<0 //一旦小于0,进入阻塞状态
block ( );
}
//V原语
V(S) `在这里插入代码片`
{
S++; //信号量加一
if S<=0 //一旦等于0,或者小于0,立即唤醒
wakeup ( );
}
生产者与消费者问题
1.问题描述
- 荷兰计算机科学家Dijkstra把广义同步问题抽象成一种“生产者与消费者问题”(producer-consumer relationship)的模型。
- 生产者与消费者可以通过一个环形缓冲池联系起来,环形缓冲池由几个大小相等的缓冲区组成,每个缓冲区容纳一个产品。
- 每个生产者可不断地每次往缓冲池中送一个生产的产品,而每个消费者则可不断地每次从缓冲池中取出一个产品。
- 指针 i 和指针 j 分别指出当前的第一个空缓冲区和第一个满缓冲区。
2.首先需要明确在这个问题中变量代表的含义:
- mutex:是保证缓冲区使用权互斥的信号量,设初值为1
- empty: 是指空缓冲区的数目;初值为n
- full: 是满缓冲区的数目;初值为0
- 整型量i和j:初值均为0,i指示空缓区序号头指针,j指示满缓 冲区序号头指针。
3.实现代码(伪代码)
/*初始化*/
semaphore mutex=1,empty=n,full=0;
int i=j=0;
item buffer [n];
- 生产者
void producer( ) /*生产者进程*/
{
while(1)
{
produce next product;
P(empty);
P(mutex);
buffer[i]=product;
i=(i+1) % n;
V(mutex);
V(full);
}
}
- 消费者
void consumer( ) /*消费者进程*/
20
{
while⑴
{
P(full);
P(mutex);
product =buffer[j];
j=(j+1) % n;
V(mutex);
V(empty);
consume the product;
}
}
void main( )
{
parbegin (producer( ), consumer( )); //消费者和生产者并发执行;先后顺序不一定,取决于当前的速度
}
以生产者为例:
- 检查缓冲区是否满; 如果满,则empty=0;执行p(empty)时,empty–;empty<0;block();生产者进入阻塞状态;(说明无法急需生产,那么睡觉。)等待消费者进入缓冲区进行消费,直到full(empty),才可继续生产
如果不满,p(empty),empty - 1,进程继续 - 通过p(mutex)判断缓冲区内是否有其他进程(消费者)在工作;若有,进入等待队列等待否则,对互斥锁上锁mutex- -,进程继续,
- 执行生产操作,生产一个新的商品放入空位
- 释放互斥锁v(mutex),mutex++;
- v(full),full+ 1 (empty与full此消彼长,总数永远是缓冲池总数n)
思考问题:
- 无论是生产者还是消费者,关于P操作的次序能否颠倒过来呢?
P(mutex);
P(empty);
P(full);
P(mutex);
回答:不可颠倒
以生产者为例,先执行p(empty)实现判断缓冲池是否为空,如果缓冲区没空,则通过p(mutex)对缓冲区进行上锁,消费者不能进入缓冲区,一旦empty=0后,说明此时缓冲池以为空,则生产者不会继续执行放入产品的操作,进入阻塞,等待消费者消费,此时还未执行p(mutex),还未对缓冲区上锁,消费者可以进入缓冲区;
而颠倒后,先执行p(mutex)消费者先进入缓冲区,并对缓冲区上锁,再执行p(empty)时发现缓冲区为空,此时缓冲区全部填满,则生产者进入阻塞状态,等待消费者进行消费,而此时消费者通过p(mutex)判断生产者在缓冲区工作,造成生产者出不来,消费者进不去的装填,二者相互等待,进程死锁。
如想有更多了解,请戳下方链接:
进程间同步互斥经典问题——生产者与消费者