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,要互斥访问F1P(full1);P(mutex1);从货架F1取出一个A零件;V(mutex1);V(empty1);//申请B零件,互斥访问F2P(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);}//车数量+1sn++;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);}//车数量+1ns++;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;思考;}}