PV操作答题基础
- 操作系统大题难度:第四章大题 > 第三章> 第二章
- 操作系统大题复习顺序:进程的同步与互斥——>虚拟分页存储管理——>文件目录、文件的物理结构
题型分类
- 生产者消费者问题——进程间关系为“生产资源-消费资源”
- 理发师问题——进程间关系为“服务-被服务”
- 读者写者问题——同类进程不互斥、异类进程互斥
- 哲学家进餐问题——只有一类进程,每个进程需要同 时拥有多种资源才能运行,需要解决死锁(2019)
- 单纯的同步问题——前驱后继图
题型1:生产者消费者
解题六步骤
- ⽣产者消费者问题( 特点:进程与进程之间是“⽣产资源⼀消耗资源”的关系) 六步骤: (打草稿)
有几类进程——每类进程对应一个函数
在每个进程内部,用中文描述进程动作[只做一次,不加while;重复操作,加while(1)]
分析每一动作之前,需要P什么?【注意隐含的互斥,例如,缓冲区访问需要加P(mutex)】只要有P,必有V,每写一个P,就要安排一个V
所有PV写完后 , 再去定义信号量,
- 定义完再思考每个信号量的初值是多少
检查多个P连续出现的地⽅, 是否可能生死锁?
- 尝试调整P顺序,若某信号量P,V操作总连续出现,中间没有其他P[破坏其他P],则信号量不可能因此产生死锁
读题检查,是否满足题目要求
例题一:配件装配
- 某工厂有两个生产车间和一个装配车间,两个生产车间分别生产A、B两种零件,装配车间的任务是把A、B两种零件组装成产品。两个生产车间每生产一个零件后,都要分别把它们送到装配车间的货架F1、F2上。F1存放零件A,F2存放零件B,F1和F2的容量均可存放10个零件。装配工人每次从货架上取一个零件A和一个零件B后组装成产品。请用P、V操作进行正确管理
思路整理
- 几类进程——一类进程对应一类函数
- 每个进程的动作——一次动作,多次动作
- 安排P,再安排V【成对出现】
- 定义信号量
- 多P连续->死锁问题?解决方法:调整顺序
- 检查
- 中文演算过程
生产车间A(){
while(){
produce A //重复动作
P(F1有空位)
P(F1_flag)
put A to F1 //访问缓冲区需要互斥
V(F1_flag)
V(A零件) //组装车间只能从货架上拿,所以在put之后释放
}
}
生产车间B(){
while(){
produce B //重复动作
P(F2有空位)
P(F2_flag)
put B to F2 //访问缓冲区需要互斥
V(F2_flag)
V(B零件)
}
}
装配车间(){
while(1){
P(A零件)
P(F1_flag)
从F1上取A
V(F1_flag)
V(F1的空位)
P(B零件)
P(F2_flag)
从F2上取B
V(F2_flag)
V(F2的空位)
组装产品
}
}
// 同步信号量
Semaphore F1_empty =10 //货架空位数信号量
Semaphore F1_empty =10
Semaphore F1_existA=0 //货架上已生产配件数量信号量
Semaphore F2_existB=0
//互斥信号量
Semaphore F1_flag=1 //货架的互斥访问信号量
Semaphore F2_flag=1
- 注意:多个P操作可能导致死锁,解决方法,同步信号量放在互斥信号量之前(互斥信号量锁粒度取小,尽量放在最里面)
例题二:生产者、消费者
- 系统中有多个生产者进程和多个消费者进程,共享一个能存放1000件产品的环形缓冲区(初始为空)。当缓冲区未满时,生产者进程可以放入其生产的一件产品,否则等待;当缓冲区未空时,消费者进程可以从缓冲区取走一件产品,否则等待。要求一个消费者进程从缓冲区连续取出10 件产品后,其他消费者进程才可以取产品。请使用信号量P,V(或 wait(),signal())操作实现进程间的互斥与同步,要求写出完整的过程,并说明所用信号量的含义和初始值。
生产者(){
while(){
生产产品
P(缓冲区空位) //同步信号量
P(m_buffer) //互斥信号量 互斥访问访问缓冲区
产品放入缓冲区
V(m_buffer)
V(产品)
}
}
消费者(){
while(){
P(Mutex) //原子性操作
for(int i=0;i<10;i++){
P(产品)
P(m_buffer) //互斥信号量 互斥访问访问缓冲区
从缓冲区取产品
P(m_buffer)
V(缓冲区空位)
}
V(Mutex)
}
}
// 同步信号量
Semaphore 缓冲区空位=1000
Semaphore Mutex=1
Semaphore 产品=0
//互斥信号量
Semaphore m_buffer=1
例题三:老小和尚打水
- 某寺庙有小和尚、老和尚若干,有一水缸,由小和尚提水入缸供老和尚饮用。水缸可容10桶水,水取自同一井中。水井径窄,每次只能容一个桶取水。水桶总数为3个。每次入缸取水仅为1桶水,且不可同时进行。试给出有关从缸取水、入水的算法描述。
思路整理
- 几个进程——两个进程:老和尚进程和小和尚进程
- 每个进程的动作——一次动作,多次动作
- 安排P,再安排V【成对出现】,注意考虑互斥访问资源:水缸和井
- 定义信号量
- 多P连续->死锁问题?解决方法:调整顺序
- 检查
小和尚(){
P(桶)
取桶
P(mutex 井)
入井打水
V(mutex 井)
P(水缸剩余空闲容量) //0<=水缸剩余容量<=10
P(mutex 缸)
倒水入缸
V(mutex 缸)
V(水缸中水余量)
还桶
V(桶)
}
老和尚(){
P(桶)
取桶
P(水缸中水余量)
P(mutex 缸)
从缸中打水
V(mutex 缸)
V(水缸剩余空闲量)
喝水
P(水缸剩余容量) //0<=水缸剩余容量<=10
还桶
V(桶)
}
//互斥信号量
Semaphore mutex 井=1
Semaphore mutex 缸=1
//非互斥信号量
Semaphore 水缸剩余空闲量=10
Semaphore 水缸中水余量=0
Semaphore 桶=3
- 上面的代码中,分析多个连续的P操作,存在死锁的问题,如老和尚三个人都持有桶,进行喝水,但是缸总没有水,小和尚进程无法获得桶,无法打水,三个老和尚喝水进程会一直循环等待,同时包括,小和尚进程也会阻塞。小和尚手中持有桶,但是3个老和尚都没有喝水,缸中剩余的容量=0,小和尚进程无法完成打水,存在循环等待问题。
- P操作顺序调整,小和尚先P(水缸剩余空闲容量),然后P(桶);老和尚先P(水缸中水余量) ,然后P(桶)
小和尚(){
P(水缸剩余空闲容量) //0<=水缸剩余容量<=10
取桶
P(mutex 井)
入井打水
V(mutex 井)
P(桶)
P(mutex 缸)
倒水入缸
V(mutex 缸)
V(水缸中水余量)
还桶
V(桶)
}
老和尚(){
P(水缸中水余量)
取桶
P(桶)
P(mutex 缸)
从缸中打水
V(mutex 缸)
V(水缸剩余空闲量)
喝水
P(水缸剩余容量) //0<=水缸剩余容量<=10
还桶
V(桶)
}
//互斥信号量
Semaphore mutex 井=1
Semaphore mutex 缸=1
//非互斥信号量
Semaphore 水缸剩余空闲量=10
Semaphore 水缸中水余量=0
Semaphore 桶=3
题型2:理发师问题
- 生产者消费者问题——进程间关系为“生产资源-消费资源”
- 理发师问题——进程间关系为“服务-被服务”
例题一:理发师问题(难度⭐⭐⭐)
- 理发店理有一位理发师、一把理发椅和n把供等候理发的顾客坐的椅子。如果没有顾客,理发师便在理发椅上睡觉,一个顾客到来时,顾客必须叫醒理发师,如果理发师正在理发时又有顾客来到,则如果有空椅子可坐,就坐下来等待,否则就离开。
例题二:面包师问题(难度⭐)
- 面包师有很多面包,由n个销售人员推销。每个顾客进店后取一个号,并且等待叫号,当一个销售人员空闲下来时,就叫下一个号。试设计一个使销售人员和顾客同步的算法
例题三:2011真题(难度⭐⭐)
例题四:咸鱼烫头沙龙
王道训练营武汉校区楼下新开了一家理发店——咸鱼烫头沙龙。这家店有 n 位发型师为客人提供一对一理 发服务。一位客人到店时,需要先取号,并等待叫号。这家店老板人很nice,没有客人时,发型师可以睡觉 休息。有客人时,只要有空闲的发型师,就叫号,让下一位客人进店理发。请使用P、V操作描述上述过程的 互斥与同步,并说明所用信号量及初值的含义。
王道训练营武汉校区楼下新开了一家理发店——咸鱼烫头沙龙。这家店有n 位发型师为客人提供一对一理发服务。一位客人到店时,需要先取号,并等待叫号。这家店老板人很nice,没有客人时,发型师可以睡觉休息。有客人时,只要有空闲的发型师,就叫号,让下一位客人进店理发。请使用P、V操作描述上述过程的互斥与同步,并说明所用信号量及初值的含义。
题型3:读者写者问题
基础代码
- 同类进程可以共享使用该资源,不同进程互斥访问该资源
- 读写互斥,写写互斥,读读不会互斥
- 可用ount记录基类进程有几个正在使用该互斥资源
- 读写平衡法:+PV排队操作(先来先服务)写读读写读
Semaphore lock=1; //用于实现对共享文件的互斥访问
Semaphore mutex=1; //用于保证对count变量的互斥访问
Semaphore ReaderCount=0; //记录当前有几个进程在读文件
writer(){
while(TRUE){
P(lock); //写文件之前上锁
写文件
V(lock); //写完文件后解锁
}
}
reader(){
while(TRUE){
P(mutex); //各个进程互斥访问ReaderCount
if(ReaderCount==0){ //第一个读者,读之前"上锁"
P(lock);
}
count++;//访问文件的读进程数+1
V(mutex);
读文件
P(mutex);//各个进程互斥访问ReaderCount
count--; //访问文件的读进程数-1
if(ReaderCount==0){ //最后一个读进程负责解锁
V(lock);
}
V(mutex);
}
}
- 该代码实现读写互斥,谢谢不互斥。但是读写不平衡:必须等所有的读进程读完之后,写进程才可以进行写入。
改进:读写平衡法
- 添加队列服务,保证读写平衡。
- 例如:写->读->读->写->读
Semaphore lock=1; //用于实现对共享文件的互斥访问
Semaphore mutex=1; //用于保证对count变量的互斥访问
Semaphore ReaderCount=0; //记录当前有几个进程在读文件
Semaphore queue=1; //读写平衡标志 用于实现“读写公平”。可以理解为一个队列,当资源暂时不可访问时,无论读进程、写进程都需要公平排队
writer(){
while(TRUE){
P(queue) //先排队
P(lock); //写文件之前上锁
写文件
V(lock); //写完之后解锁
V(queue); //唤醒下一个队头进程
}
}
reader(){
while(TRUE){
P(queue); //先排队
P(mutex); //各个进程互斥访问ReaderCount
if(ReaderCount==0){ //第一个读者,读之前"上锁"
P(lock);
}
count++;//访问文件的读进程数+1
V(mutex);
V(queue); //唤醒队头进程
读文件
P(mutex);//各个进程互斥访问ReaderCount
count--; //访问文件的读进程数-1
if(ReaderCount==0){ //最后一个读进程负责解锁
V(lock);
}
V(mutex);
}
}
足球俱乐部更衣问题
某男⼦⾜球俱乐部,有⾮常多的教练、队员。每次⾜球训练开始之前,教练、球员都需要先进⼊更⾐室换⾐服,可惜俱乐部只有⼀个更⾐室。教练们脸⽪薄,⽆法接受和别⼈共⽤更⾐室。队员们脸⽪厚,可以和其他队员⼀起使⽤更⾐室。更⾐完成后,才可以参加⾜球训练。
- 起初,俱乐部规定:如果队员和教练都要使⽤更⾐室,则应该让教练优先。请使⽤P、V操作描述上述过程的互斥与同步,并说明所⽤信号量及初值的含义
- 教练就是写者,队员就是读者,不过是读者写者问题换了个⻢甲⽽已。按照题⽬要求,要求实现“写优先”,这种⽅法可能导致读者饥饿。
semophore readLock=1; //互斥信号量,⽤于给读者“上锁”
semophore writeLock=1; //互斥信号量,⽤于给写者“上锁”
semophore rmutex=1; //互斥信号量,实现对readCount的互斥访问
semophore wmutex=1; //互斥信号量,实现对writeCount的互斥访问
int readCount=0, writeCount=0; //读者、写者的数量
//读者进程(在这个题⾥就是可以多⼈⼀起共⽤更⾐室的队员们)
Reader(){
while(1){
P(readLock); //每个读者到达时先对 read 上锁
P(rmutex);
readCount++;
if(readCount==1) P(writeLock); //第⼀个开始读的读者对写者上锁
V(rmutex);
V(readLock); //每个读者正式开始读之前对 read 解锁
队员使⽤更⾐室; //读者读⽂件
P(rmutex);
readCount--;
if(readCount==0) V(writeLock); //最后⼀个读完的读者对写者解锁
V(rmutex);
队员参加⾜球训练;
}
}
//写者进程(对应必须独享更⾐室的教练们)
Writer(){
while(1){
P(wmutex);
writeCount++;
if(writeCount==1) P(readLock); //第⼀个到达的写者对读者上锁,这⼀步是实现“写优先”的关键
V(wmutex);
P(writeLock); //每个写者开始写之前都要对其他写者上锁,保证写者之间互斥
教练使⽤更⾐室; //写者写⽂件
V(writeLock);
P(wmutex);
writeCount--;
if(writeCount==0) V(readLock); //最后⼀个写者写完之后,对读者解锁
V(wmutex);
教练参加⾜球训练;
}
}
后来,因很多队员⽆法忍受教练优先的特权现象,俱乐部更改了规定——为体现“⼈⼈平等”的团队作⻛,队员和教练应遵循“先到先⽤”的原则,公平地使⽤更⾐室。请使⽤P、V操作描述上述过程的互斥与同步,并说明所⽤信号量及初值的含义
Semahore readLock=1; //互斥信号量,⽤于给读者“上锁” semophore writeLock=1; //互斥信号量,⽤于给写者“上锁” semophore rmutex=1; //互斥信号量,实现对readCount的互斥访问 semophore wmutex=1; //互斥信号量,实现对writeCount的互斥访问 int readCount=0, writeCount=0; //读者、写者的数量 //读者进程(在这个题⾥就是可以多⼈⼀起共⽤更⾐室的队员们) Reader(){ while(1){ P(readLock); //每个读者到达时先对 read 上锁 P(rmutex); readCount++; if(readCount==1) P(writeLock); //第⼀个开始读的读者对写者上锁 V(rmutex); V(readLock); //每个读者正式开始读之前对 read 解锁 队员使⽤更⾐室; //读者读⽂件 P(rmutex); readCount--; if(readCount==0) V(writeLock); //最后⼀个读完的读者对写者解锁 V(rmutex); 队员参加⾜球训练; } //写者进程(对应必须独享更⾐室的教练们) Writer(){ while(1){ P(wmutex); writeCount++; if(writeCount==1) P(readLock); //第⼀个写者对读者上锁——实现“写优先”的关键 V(wmutex); P(writeLock); //每个写者开始写之前都要对其他写者上锁,保证写者之间互斥 教练使⽤更⾐室; //写者写⽂件 V(writeLock); P(wmutex); writeCount--; if(writeCount==0) V(readLock); //最后⼀个写者写完之后,对读者解锁 V(wmutex); 教练参加⾜球训练; } }
例题一:汽车过桥
例题二:看电影问题
题型4:哲学家问题
- 哲学家问题关键点是限制并行,主要是三种思路
限制申请资源的顺序(解法一不通用,不建议使用) 如:规定单号哲学家先取左筷子,双号先取右筷子限制并发进程数(解法二通用,但并发度不高,不建议使用) 如:最多允许4个哲学家同时进餐- 让进程一口气取得所有资源,再开始运行(解法三很通用,且并发度高,建议使用) 如:哲学家只有能够取得两个筷子的时候才会就餐。
2019年真题
43.(8分)有n(n≥3)位哲学家围坐在一张圆桌边,每位哲学家交替地就餐和思考。在圆桌中心有m(m≥1)个碗,每两位哲学家之间有一根筷子。每位哲学家必须取到一个碗和两侧的筷子后,才能就餐,进餐完毕,将碗和筷子放回原位,并继续思考。为使尽可能多的哲学家同时就餐,且防止出现死锁现象,请使用信号量的P、V操作[wait()、signal()操作]描述上述过程中的互斥与同步,并说明所用信号量及初值的含义。
int wan=m;//m个碗
int kuai[n]={1,...,1}
Semaphore lock=1;
Semaphore mutex=1;
P_i(){
P(lock)
while(1){
if(kuai[(i-1)%n]==1 && kuai[i]==1 && wan>=1){
取三种资源
V(lock); //资源获取成功,释放锁 跳出循环
break;
}
V(lock); //资源获取失败,解锁,再次尝试
}
吃饭
P(mutex) //原子性操作
归还资源
V(mutex)
}
干饭人问题
- 俗话说,“⼲饭⼈,⼲饭魂,⼲饭⼈吃饭得⽤盆”。⼀荤、⼀素、⼀汤、⼀⽶饭,是每个⼲饭⼈的标配。饭点到了,很多⼲饭⼈奔向⻝堂。每个⼲饭⼈进⼊⻝堂后,需要做这些事:拿⼀个盆打荤菜,再拿⼀个盆打素菜,再拿⼀个盆打汤,再拿⼀个盆打饭,然后找⼀个座位坐下⼲饭,⼲完饭把盆还给⻝堂,然后跑路。现在,⻝堂⾥共有N个盆,M个座位。请使⽤P、V操作描述上述过程的互斥与同步,并说明所⽤信号量及初值的含义。
第⼆种解决思路【不推荐】。在哲学家问题中,共有5个哲学家,如果我们限制“最多允许4个哲学家同时进餐”,那么⾄少会有⼀个哲学家可以同时获得左右两只筷⼦,并顺利进餐,从⽽预防了死锁。同样的思路可以迁移到⼲饭⼈问题中。每个⼲饭⼈需要同时持有4个盆才能⼲饭,那么最糟糕的情况是每个⼲饭⼈都持有3个盆,同时在等待第四个盆。此时,但凡再多⼀个盆,就⾄少能有⼀个⼲饭⼈可以顺利⼲饭,就
不会死锁。因此我们可以限制同时抢盆的⼈数为 x,那么只要满⾜ 3x + 1 ≤ N,则⼀定不会发⽣死锁,可得 x≤ (N-1)/3。- 这种做法,限制了⼈数上限,且先拿盆,再占座,⼀定不会发⽣死锁。当然,如果先占座、后拿盆,也不会死锁。事实上,如果座位的数量满⾜ seat ≤ (N-1)/3,那么甚⾄可以不设置专⻔的信号量x,完全可以先占座,后拿盆,也⼀定不会死锁。因为座位的数量就可以限制同时抢盆的⼈数。
semaphore pot=N; //同步信号量,⽤于表示“盆”资源,⻝堂⾥总共有N个盆
semaphore seat=M; //同步信号量,⽤于表示“作为”资源,⻝堂⾥总共有M个座位
semaphore x=(N-1)/3; //同步信号量x,⽤于表示最多允许多少个⼈同时⼲饭。(N-1)/3 向下取整
//⼲饭⼈进程
EatMan(){
P(x); //进⻝堂拿盆之前,先看看是否已到达⼈数上限
进⻝堂;
P(pot); //拿⼀个盆
打荤菜;
P(pot); //拿⼀个盆
打素材;
P(pot); //拿⼀个盆
打汤;
P(pot); //拿⼀个盆
打饭;
P(seat); //占个座
⼲饭;
V(seat); //让出座位
V(pot); //归还⼲饭盆
V(pot);
V(pot);
V(pot);
V(x);
离开⻝堂;
}
第三种解决思路——仅当⼀个哲学家左右两边的筷⼦都可⽤时才允许哲学家拿
筷⼦。破坏了“请求和保持”条件,采⽤“静态分配”的思想,让进程⼀⼝⽓获得所有资源,再开始运⾏。Semaphore mutex=1; //互斥信号量,保证所有进程对 pot 变量、seat 变量的访问是互斥的。 semaphore pot=N; //⽤于表示“盆”资源,⻝堂⾥总共有N个盆 semaphore seat=M; //⽤于表示“作为”资源,⻝堂⾥总共有M个座位 //⼲饭⼈进程 EatMan(){ 进⻝堂; P(mutex); for(int i=0;i<4;i++){ P(pot); //⼀⼝⽓拿四个盆,并占座 } P(seat); V(mutex); 打荤菜; 打素材; 打汤; 打饭; ⼲饭; V(seat); //让出座位 for(int i=0;i<4;i++){ P(pot); //归还⼲饭盆 } 离开⻝堂; }
总结:“使⽤int变量表示资源”的哲学家进餐问题解题模板,使⽤这种⽅法解决哲学家进餐问题,可以保证进程间的并发度最⾼,但缺点是进程会陷⼊“忙等”。
题型5:单纯的同步问题
2022真题
- (2022)某进程的两个线程T1和T2并发执行A、B、C、D、E和F共6个操作,其中T1执行A、E和F,T2执行B、C和D。题46图表示上述6个操作的执行顺序所必须满足的约束:C在A和B 完成后执行,D和E在C完成后执行,F在E完成后执行。请使用信号量的wait()、signal()操作描述 T1和 T2 之间的同步关系,并说明所用信号量的作用及其初值。
T1(){
A
V(AC)
P(CE)
E
P(EF) //天然顺序关系 可省略
F
}
T2(){
B
V(BC) //天然顺序关系 可省略
P(AC)
P(BC) //天然顺序关系 可省略
C
V(CE)
P(CD) //天然顺序关系 可省略
D
}
- 整理后为
T1(){
A
V(AC)
P(CE)
E
F
}
T2(){
B
P(AC)
C
V(CE)
D
}
2020真题
- (2020)现有5个操作A、B、C、D和E,操作C必须在A和B完成后执行,操作E必须在C和D 完成后执行,请使用信号量的 wait())、signal()操作(P、V操作)描述上述操作之间的同步关系,并说明所用信号量及其初值
A(){
A
V(A)
}
B(){
B
V(B)
}
C(){
P(A) P(B)
C
V(C)
}
D(){
D
V(D)
}
E(){
P(C)P(D)
E
}
PV操作终极
PV的本质是同步互斥,题目考察的是分析同步互斥关系的能力。
题型分类的依据-----同步问题!互斥问题!同步互斥综合问题
同步问题
互斥问题
thread1--thread2 w写写互斥
thread1--thread3 w读写互斥
thread2--thread3 z读写互斥
互斥问题-读者写者
- 为什么读者写者被单独抽象成一种模型?因为它体现了典型的寻找互斥关系的方法—读写互斥写写互斥读读不需要互斥
- what else?读者写者问题体现了遇到分支情况时候的处理方法
互斥问题-哲学家干饭
- 参看PV操作答题基础——题型4:哲学家问题
同步互斥综合题
- 难度差异比较大,,可以出简单题,也可以出难题–生产者消费者
- 既有同步关系,,也有互斥关系
- 为每一组同步互斥关系设置一个信号量
互斥问题加大难度
同步问题加大难度
- 最典型的同步信号量---->empty && full
- empty:缓冲区不满—>生产者才能放产品—>放产品之前要求full>0
- full:缓冲区不为空—> 消费者才能取产品—>取产品之前要求empty>0
- 设有两个生产者进程A、B和一个销售者进程C,它们共享一个无限大的仓库,生产者每次循环生产一件产品,然后入库供销售者销售;销售者每次循环从仓库取出一件产品进行销售。如果不允许同时入库,也不允许边入库边出库,而且要求生产产品A和B的件数关系满足:
-n≤A的件数-B的件数≤m
,其中n、m 是正整数,但对仓库中产品A和产品B的件数无上述要求。用信号量机制写出A、B、C三个进程的工作流程。