1.生产者消费者问题——进程间关系为“生产资源-消费资源”
解题步骤
- 分析有几类进程——每类进程对应一个函数
- 在每一个函数内部可以用中文描述动作(如果动作只做一次,就不用加while循环,如果动作要重复,就要加while循环)
- 分析每一个动作在做之前,需不需要P操作**(很多动作要先获取某种资源),(注意隐含的互斥,比如缓冲区的访问,要加P(mutex))**比如去食堂吃饭要先打饭,只要有P操作,必有V操作
- 所有PV写完以后,再去定义信号量,定义完信号量后再去思考每一个信号量的初值为多少
- 检查多个P连续出现的地方,是否可能产生死锁**(可以尝试调整P操作顺序)。如果某一个信号量PV操作总是连续出现,中间没有夹杂其他P操作,则不可能因为此信号量而产生死锁(破坏了请求和保持条件)**
- 读题检查,是否符合题意
//不夹杂其余P操作
P(mutex);
动作;
V(mutex);
//夹杂其余P操作
P(mutex);
P(full);
动作;
V(empty);
V(mutex;
Semaphore rice=0;//米饭
Semaphore one=100;//碗 然后把草稿中,米饭改为rice,碗改为one
同学(){
P(米饭);
吃饭;
V(碗);
}
大师傅(){
while(1){
P(碗);
打饭;
V(米饭);
}
}
例题1 生产零件,组装产品问题
生产者消费者问题
有三个进程
互斥:如果生产工人把产品要访问货架F1,那么装配工人就不能访问货架F1了
互斥:如果货架放满了,就不能再放了
Semaphore empty1=10;//F1还可以放多少A零件;
Semaphore full1=0;//F1已经放了多少A零件;
Semaphore empty2=10;//F2还可以放多少B零件
Semaphore full2=0;//F2已经放了多少B零件
Semaphore mutex1=1;//对货架F1互斥访问
Semaphore mutex2=1;//对货架F2互斥访问
A(){
while(1){
生产A零件;
//如果要把A零件放到货架上,则要看F1是否还有空位,并且要互斥访问F1,因为装配工人每一次只能把一个零件放到一个货架上
// F1和F2要互斥访问
P(empty1);
p(mutex1);
把A零件放到货架F1;
V(full1);
V(mutex1);
}
}
B(){
while(1){
生产B零件;
//如果要把B零件放到货架上,则要看F2是否还有空位,而且要互斥访问F2,因为装配工人每一次只能把一个零件放到一个货架上
// F1和F2要互斥访问
P(empty2);
p(mutex2);
把B零件放到货架F2;
V(full2);
V(mutex2);
}
}
装配车间(){
while(1){
//要申请A,要互斥访问F1
P(full1);
P(mutex1);
从货架F1取出一个A零件;
V(mutex1);
V(empty1);
//申请B零件,互斥访问F2
P(full2);
P(mutex2);
从货架F2取出一个B零件;
V(empty2);
V(mutex2);
把A零件和B零件组合成一个产品;
}
}
标准答案
例题2 和尚取水问题
互斥访问:缸,井
而且去缸里面取水的时候,要看缸里面还有没有水
小和尚相当于生产者 老和尚相当于消费者
老和尚也要去缸里面拿桶取水,然后饮水
Semaphore gang=1;//互斥访问缸;
Semaphore jing=1;//互斥访问井;
Semaphore empty=10;//缸中还能提多少水
Semaphore full=0;//已经提了多少水
Semaphore tong=3;//桶
小和尚(){
while(1){
//从井中提水首先要有桶
P(empty);//是否还能继续提水
P(tong); //提水前要申请桶资源 P(桶);
P(jing);
从井中提水;
V(jing);
//互斥访问缸
//如果缸中水满了,就不能继续提水
P(gang); //提水入缸
提水入缸;
V(gang);
V(tong);
V(full);
}
}
老和尚(){
while(1){
//要有桶才能喝水
//缸中要有水才能喝水
//要互斥访问缸
P(full);
P(tong);//申请桶
P(gang);
拿着桶到缸里面取水
V(gang);
V(empty);
饮水;
V(tong);
}
}
标准答案
2.理发师问题——进程间关系为“服务-被服务”
可以看成同步问题 服务业
你 理发师
生产者 消费者
通常要用一个变量来记录等待的顾客有多少个
理发师(){
是否有顾客?
无——>睡觉
有——>提供服务
while(1){
if(num>0){
//说明有顾客
提供服务
}else{
睡觉;
}
}
}
顾客(){
理发师在忙?
忙——>检查座位够不够,如果座位够,就坐下来等。如果不够,就离开
不忙——>叫醒理发师
}
3.读者写者问题——同类进程不互斥、异类进程互斥
:::info
- 读者写者问题主要是解决互斥,他的访问关系分为两种题型,一种是可以同时访问(读和读),另外一种是必须互斥访问(写和读,写和写),因为多种关系,所以引入计数器count
- 互斥:mutex。写过程是不容其他过程的,所以直接P(mutex),而读过程P(mutex)受计数器count影响。第一个读进程才需要P操作,最好一个读进程退出时负责V操作
- rw:读写之间的互斥,对文件互斥访问。
:::
例题1:车辆在桥上行驶问题
Semaphore bridge=1;
从南向北的车(){
//车要在桥上行驶,要占用桥资源,互斥访问桥
P(bridge);
在桥上行驶;
车从桥上驶离;
V(bridge);
}
从北向南的车(){
//车要在桥上行驶,要占用桥资源,互斥访问桥
P(bridge);
在桥上行驶;
车从桥上驶离;
V(bridge);
}
读者写者问题
允许一个方向有多辆车行驶
不允许两车交会
要占用桥资源
同类进程共享资源,不同类进程互斥使用资源
Semaphore bridge=1;//互斥访问桥
int sn=0;//从南向北的车的数量
int ns=0;//从北向南的车的数量
Semaphore mutex_sn=1;//互斥访问sn
Semaphore mutex_ns=1;;/互斥访问ns
从南向北的车(){
//使用sn要申请
P(mutex_sn);
//如果车的数量为0,则要申请桥资源,否则直接在桥上行驶
if(sn==0){
P(bridge);
}
//车数量+1
sn++;
V(mutex_sn);
车在桥上行驶;
//如果最后一辆车行驶过去,则释放桥
P(mutex_sn);
sn--;
if(sn==0){
v(bridge);
}
V(mutex_sn);
V(sn);
}
从北向南的车(){
//使用ns要申请
P(mutex_ns);
//如果车的数量为0,则要申请桥资源,否则直接在桥上行驶
if(ns==0){
P(bridge);
}
//车数量+1
ns++;
V(mutex_ns);
车在桥上行驶;
//如果最后一辆车行驶过去,则释放桥
P(mutex_ns);
ns--;
if(ns==0){
v(bridge);
}
V(mutex_ns);
V(ns);
}
例题2:猴子穿越峡谷
同一个方向可以有多只狒狒通过
同一时间不允许两个方向都有狒狒通过
读者写者问题
Semaphore rope=1;//互斥访问绳索
Semaphore mutex_es=1;//用于保护es
Semaphore mutex_se=1;//保护se
int es=0;//从东到西穿越的狒狒个数
int se=0;//从西到东穿越的个数
从东到西的狒狒(){
//如果是第一只要穿越,要占用绳索
P(mutex_es);
if(es==0){
P(rope);
}
es++;
V(mutex_es);
攀住绳子穿越峡谷;
//如果最后一只穿越过去,则释放绳索
P(mutex_es);
es--;
if(es==0){
V(rope);
}
V(mutex_es);
V(rope);
}
从西到东的狒狒(){
//如果是第一只要穿越,要占用绳索
P(mutex_se);
if(se==0){
P(rope);
}
se++;
V(mutex_se);
攀住绳子穿越峡谷;
//如果最后一只穿越过去,则释放绳索
P(mutex_se);
se--;
if(se==0){
V(rope);
}
V(mutex_se);
V(rope);
}
例题3:录像厅问题
观看不同录像片的观众互斥使用录像厅
4.哲学家进餐问题——只有一类进程,每个进程需要同时拥有多种资源才能运行
思路
以后,如果在考试中,遇到一个进程需要一口气取走所有资源的题目,可以利用互斥变量mutex,
P(mutex);
申请各种资源;
V(mutex);
第三个思路最通用:
①将每种资源通过int类型的变量定义(使用变量将资源数量具象化)
②在进程开始运行时,先使用P(MUTEX)操作实现对各种资源进行互斥的访问,目的是逐一判断当前进程所需的各种资源是否满足运行的需要
③如果资源都满足,则拿走这些资源(一口气拿走),然后V(MUTEX),再执行动作
循环条件下:如果只要有一个资源不满足,则V(MUTEX),然后结束此次循环,进行下一次循环(continue);
只进行一次:如果只要有一个资源不满足,则使用goto语句返回函数的第一句重新进行判断(手动实现循环)
其中②实现同一时间只可能有一个进程在进行判断/拿走临界资源
最后完成动作归还资源时也要进行上锁,保证归还动作一气呵成的完成
第一步:定义锁 semaphore mutex = 1; //互斥信号量,同时仅允许一个进程判断并拿走临界资源
第二步:定义资源数目 int a = n; //用int类型表示各种资源
int b = m;
代码模板
Philosopher () { //进行多次,while
第三步:一口气拿走所有资源 while (1) {
P(mutex);
if (所有资源都足够) { //资源够用,将所需资源分配给该进程,并解锁
所有资源的int值减少;
取xxx资源//一口气拿完所有资源
V(mutex);//拿完资源以后解锁
break;//跳出循环
}
如果资源不够,就解锁,再循环尝试一次
V(mutex);
}//while循环结束
第四步:做进程该做的事,比如吃饭
第五步:一口气归还所有资源
P(mutex); //完成动作后,归还资源
归还所有资源,所有资源的int值增加
V(mutex);
}
干饭问题
俗话说,“干饭人,干饭魂,干饭人吃饭得用盆"。一荤、一素、一汤、一米饭,是每个干饭人的标配。饭点到了,很多干饭人奔向食堂。每个干饭人进入食堂后,需要做这些事:拿一个盆打荤菜,再拿一个盆打素菜,再拿一个盆打汤,再拿一个盆打饭,然后找一个座位坐下干饭,干完饭把盆还给食堂,然后跑路。现在,食堂里共有N个盆,M个座位。请使用P、V操作描述上述过程的互斥与同步,并说明所用信号量及初值的含义。
下面我们模仿哲学家进餐问题的第二种解决思路。在哲学家问题中,共有5个哲学家,如果我们限制“最多允许4个哲学家同时进餐",那么至少会有一个哲学家可以同时获得左右两只筷子,并顺利进餐,从而预防了死锁。
同样的思路可以迁移到干饭人问题中。每个干饭人需要同时持有4个盆才能干饭,那么最糟糕的情况是每个干饭人都持有3个盆,同时在等待第四个盆。此时,但凡再多一个盆,就至少能有一个干饭人可以顺利干饭,就不会死锁。因此我们可以限制同时抢盆的人数为x,那么只要满足3x+1≤N,则一定不会发生死锁,可得x≤(N-1)/3。参考代码如下:
上面这种做法,限制了人数上限,且先拿盆,再占座,一定不会发生死锁。当然,如果先占座、后拿盆,也不会死锁。事实上,如果座位的数量满足seat ≤ (N-1)/3,那么甚至可以不设置专门的信号量x,完全可以先占座,后拿盆,也一定不会死锁。因为座位的数量就可以限制同时抢盆的人数。
下面我们再模仿哲学家问题的第三种解决思路一—仅当一个哲学家左右两边的筷子都可用时才允许哲学家拿筷子。其实就是破坏了请求和保持条件,采用"静态分配"的思想,让进程一口气获得所有资源,再开始运行。代码如下:
2019年真题
Semaphore mutex=1;//互斥
int wan=m;//碗
int chop[n]={1,1,1...,1};//筷子
哲学家(){
//一个哲学家把所有资源都获得以后,其他哲学家才能取资源
while(1){
P(mutex);
//要先申请碗,先判断资源足不足够,如果资源不够就要释放锁
if(wan<=0) {
V(mutex);
continue;
}
//判断筷子资源够不够
if(!(chop[i]==1&&chop[i+1]==1)){
V(mutex);
continue;
}
//说明资源足够
chop[i]=0;//取走左边筷子
chop[i+1]=0;//取走右边筷子
wan--;
V(mutex);
进餐
还筷子
chop[i]=1;
chop[i+1]=1;
思考;
}
}