一、生产者-消费者问题
生产者消费者问题(英语:Producer-consumer problem),也称有限缓冲问题(英语:Bounded-buffer problem),是一个多线程同步问题的经典案例。该问题描述了共享固定大小缓冲区的两个线程——即所谓的“生产者”和“消费者”——在实际运行时会发生的问题。生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,消费者也在缓冲区消耗这些数据。该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据。
.
要解决该问题,就必须让生产者在缓冲区满时休眠(要么干脆就放弃数据),等到下次消费者消耗缓冲区中的数据的时候,生产者才能被唤醒,开始往缓冲区添加数据。同样,也可以让消费者在缓冲区空时进入休眠,等到生产者往缓冲区添加数据之后,再唤醒消费者。通常采用进程间通信的方法解决该问题。如果解决方法不够完善,则容易出现死锁的情况。出现死锁时,两个线程都会陷入休眠,等待对方唤醒自己。该问题也能被推广到多个生产者和消费者的情形。
问题分析:
①、关系分析:
同步关系
1、在缓冲区为空时,消费者不能再进行消费
2、在缓冲区为满时,生产者不能再进行生产
互斥关系:
1、在一个线程进行生产或消费时,其余线程不能再进行生产或消费等操作,即保持线程间的同步
②、关系实现
生产者 | 消费者 | ||
临界区互斥信号量(mutex) | 0 | P(mutex); 生产操作; V(mutex); | P(mutex); 消费操作; V(mutex); |
缓存区剩余缓存数(empty) | n | P(empty); P(mutex); 生产操作; V(mutex); | P(mutex); 生产操作; V(empty); V(mutex); |
缓存区缓存数 | 1 | P(empty); P(mutex); 生产操作; V(mutex); V(full) | P(full); P(mutex); 生产操作; V(empty); V(mutex); |
伪代码实现:
semaphore mutex=1; //设置临界区互斥信号量
semaphore empty=n; //缓存区剩余缓存数量 生产者私有信号
semaphore full=0; //缓存区是否为空 消费者私有信号
生产者:
while(1){
。。。
p(empty); //分配缓存
P(mutex); //进入缓存临界区
缓存操作; //缓存
V(mutex); //解锁缓存临界区
P(full); //缓存的数据量加一
。。。
}
消费者:
while(1){
。。。
V(empty); //分配缓存内容
P(mutex); //进入缓存临界区
缓存操作; //读取缓存
V(mutex); //解锁缓存临界区
P(full); //收回缓存
。。。
}
互斥P操作必须在同步P操作之后
二、多生产者—多消费者问题
桌子上有一个盘子,每次只能放一个水果。爸爸专门向盘子中放苹果,妈妈专门放桔子,儿子等着吃盘中的桔子,女儿等着吃苹果。用P、V操作实现他们之间的同步。
多生产者多消费者问题和单生产者单消费者问题本质上是差不多的。
生产者与消费者公用一个信号量来限制使用临界区资源
不同的消费者与不同的生产者对各自的产品与消费对象都用一个独立的信号量来控制,避免空等和错误使用
问题分析:
①、进程分析:
四类进程:父、子、母、女
②、关系分析:
同步关系:
1、当盘子为空时,母亲或父亲可以放苹果
2、当盘子有苹果时,女儿取出
3、当盘子有橘子时,儿子取出
互斥关系:
1、父亲、母亲、儿子、女儿只能有一个访问盘子
③、信号量:
同步信号量:apple=0;oringe=0;empty=0;
互斥信号量:mutex=0;
semaphore mutex=1; //设置临界区互斥信号量
semaphore empty=1; //缓存区是否为空
semaphore apple=0, oringe=0; //缓存区是否有水果
父:
while(1){
。。。
p(empty); //分配缓存
P(mutex); //进入缓存临界区
放苹果; //放苹果
V(mutex); //解锁缓存临界区
V(apple); //苹果加一
。。。
}
母:
while(1){
。。。
P(empty); //分配缓存内容
P(mutex); //进入缓存临界区
缓存操作; //读取缓存
V(mutex); //解锁缓存临界区
V(oringe); //橘子加一
。。。
}
子:
while(1){
。。。
P(oringe); //橘子减一
P(mutex); //进入缓存临界区
拿橘子; //拿橘子
V(mutex); //解锁缓存临界区
V(empty); //盘子取空
。。。
}
女:
while(1){
。。。
P(oringe); //橘子减一
P(mutex); //进入缓存临界区
拿橘子; //拿橘子
V(mutex); //解锁缓存临界区
V(empty); //盘子取空
。。。
}
三、抽烟者问题
吸烟者问题:假设一个系统有三个抽烟者进程和一个供应者进程。每个抽烟者不停地卷烟并抽掉,但是要卷起并抽掉一支烟,抽烟者需要三种材料:烟草、纸和胶水。三个抽烟者中,第一个拥有烟草,第二个拥有纸,第三个拥有胶水。供应者无限地提供三种材料,供应者每次讲两种材料挡在桌子上,拥有剩下那种材料的卷烟者卷一根烟并抽掉它,并给供应者一个告诉完成信号,供应者就会放另外两种材料在桌上,这种过程一直重复。
问题分析:
进程分析:四个进程:抽烟者1、抽烟者2、抽烟者3、供应者
①、互斥分析:
抽烟者1、2、3和供应者是互斥关系
②、同步分析:
抽烟者1、2、3需要等待供应者供应材料
供应者需要等待抽烟者发出“抽完”的指令
③、信号量:
互斥信号:mutex=0;
同步信号:finash=1;glue=0;tobacco=0;paper=0;
semaphore mutex=1; //设置临界区互斥信号量
semaphore paper=0, tobacco=0;glue=0; //烟草、打火机、卷纸信号
semaphore finash=0;
供应者:
while(1)
{
p(empty);
p(mutex);
供应材料。
v(mutex)
if(材料为A) v(glue);
if(材料为B) v(tobacco);
if(材料为C) v(paper);
V(finash)
}
抽烟者1:
while(1){
。。。
p(glue); //查看是否有自己需要的东西
P(mutex); //上手
抽烟;
V(mutex); //解锁缓存临界区
P(finash); //取空
。。。
}
抽烟者3:
while(1){
。。。
p(paper); //查看是否有自己需要的东西
P(mutex); //上手
抽烟;
V(mutex); //解锁缓存临界区
P(finash); //取空
。。。
}
抽烟者2:
while(1){
。。。
p(tobacco); //查看是否有自己需要的东西
P(mutex); //上手
抽烟;
V(mutex); //解锁缓存临界区
P(finash); //取空
。。。
}