标签:count 同步 管程 chopstick 进程 mutex 缓冲区 semaphore 2.3
2.3.4 经典同步问题
1.生产者-消费者问题
问题描述:
一组生产者进程和一组消费者进程共享一个初始为空,大小为n的缓冲区
1.只有缓冲区没满的时候,生产者才能将消息放入缓冲区,否则必须等待
2.只有缓冲区不空的时候才能从中取出消息,否则必须等待
3.由于缓冲区是临界资源,它只允许一个生产者放入消息,或者从一个消费者中取出消息
问题分析:
1)关系分析:“1.”生产者(削苹果的人)与消费者(吃苹果的人)是同步关系,当缓冲区(桌子)的资源(苹果)满的时候,只有消费者消费了一个资源(吃了一瓣苹果),生产者才能将产品放在缓冲区中:
所以,当缓冲区满的时候,消费者移走缓冲区资源和生产者将资源放入缓冲区是同步关系(一前一后)
“2.”同上,缓冲区空的时候,生产者将资源放到缓冲区和消费者将资源移出缓冲区是同步关系(一前一后)
“3.”缓冲区是临界资源,往缓冲区内放入/取走产品需要互斥(一般在一个进程中完成)
2)整理思路:生产者每次要生产一个产品(v),消耗一个缓冲区(p)
消费者每次要消耗一个产品(p),释放一个缓冲区(v)
3)信号量设置:mutex为互斥信号量设为1,full用来记录满的缓冲区的个数设为0,empty用于记录空的缓冲区的个数,记作n。
代码描述:
semaphore mutex=1;
semaphore full=0;
semaphore empty=n;
producer(){
while(1){
produce a series of data; //生产产品
P(empty); //获取一个空闲的缓冲区(第一次写错了)
P(mutex); //与producer进程下的V(mutex)是一对,不能与P(empty)交换位置
add data to buffer; //将生产的产品放入缓冲区
V(mutex);
V(full);
}
}
consumer(){
while(1){
P(full); //获取一个满的缓冲区(第一次写错了)
P(mutex);
remove an item from buffer; //移走缓冲区中的产品
V(mutex);
V(empty); //空闲缓冲区+1
consume the item; //消费产品
}
}
而两个进程各自的V(mutex)和V(full),V(mutex)和V(empty)操作可以交换
2.多生产者和多消费者问题
问题描述:
桌上有一只盘子,每次只能向其中放入一个水果。
爸爸向盘子中只放苹果,妈妈向盘子中只放橘子,儿子只吃橘子,女儿只吃苹果。
只有盘子为空时,爸爸妈妈才可以向盘子中放入 一个水果。
仅当盘子中有自己需要的水果时,儿子或者女儿才可以从盘子中取出水果。用PV操作实现以上过程
关系分析:
互斥关系:对缓冲区(盘子)的访问要互斥的进行
同步关系:
- 父亲放入苹果和女儿取出苹果为同步关系
- 母亲放入橘子和女儿取出橘子是同步关系
- 盘子为空事件->放入水果事件
(容易忽略的为第3条)
伪代码:
semaphore mutex=1; //互斥信号量
semaphore apple=0; //苹果的同步信号量
semaphore orange=0; //橘子的同步信号量
semaphore plate=0; //盘子的水果有多少的同步信号量
dad(){
while(1){
拿出一个苹果
P(mutex);
放在桌子上
V(mutex);
V(apple);
V(plate);
}
}
mom(){
while(1){
拿出一个橘子
P(mutex);
放在桌子上
V(mutex);
V(orange);
V(plate);
}
}
daughter(){
while(1){
P(plate);
P(apple);
P(mutex);
桌子上拿走一个苹果
V(mutex);
吃掉
}
}
son(){
while(1){
P(plate);
P(orange);
P(mutex);
桌子上拿走一个橘子
V(mutex);
吃掉
}
}
上述的问题中,如果不设置mutex
信号量,也可以互斥的访问盘子,但是如果缓冲区为2,则必须设置互斥的访问缓冲区
总结一下,不管缓冲区为多少,尽可能设置mutex
记录型信号量
3.吸烟者问题
问题描述:
假设一个系统有三个抽烟者进程和一个供应者进程。每个抽烟者不停地卷烟并抽掉它,但是要卷起并抽掉一支烟,抽烟者需要有三种材料:烟草、纸和胶水。三个抽烟者中,第一个拥有烟草、第二个拥有纸、第三个拥有胶水。供应者进程无限地提供三种材料,供应者每次将两种材料放桌子上,拥有剩下那种材料的抽烟者卷一根烟并抽掉它,并给供应者进程一个信号告诉完成了,供应者就会放另外两种材料再桌上,这个过程一直重复(让三个抽烟者轮流地抽烟)
问题分析:
这个题目本质上也是生产者消费者问题(单个生产者可以生产多个消费品的过程):
假设:
组合1:卷纸+胶水
组合2:烟草+胶水
组合3:卷纸+烟草
同步关系(事件的相互关系):
供应者提供组合1->吸烟者1拿走组合1
供应者提供组合2->吸烟者2拿走组合2
供应者提供组合3->吸烟者3拿走组合3
发出完成信号->供应者将下一个组合放到桌上(易忽略)
互斥关系:
对桌子上资源的访问只能互斥的进行
伪代码:
semaphore combination1=0;
semaphore combination2=0;
semaphore combination3=0;
semaphore left=0; //桌子上剩余组合数
semaphore mutex=1;
int i=0;
producer(){
while(1){
if(i==0){
P(mutex);
把组合1放到桌子上
V(mutex);
V(combination1);
}else if(i==1){
P(mutex);
把组合2放到桌子上
V(mutex);
V(combination2);
}else if(i==2){
P(mutex);
把组合3放到桌子上
V(mutex);
V(combination3);
}
i=(i+1)%3;
V(left);
}
}
smoker1(){
while(1){
if(i==0){
P(left);
P(combination1);
P(mutex);
拿走组合1
V(mutex);
}
}
}
smoker2(){
while(1){
if(i==1){
P(left);
P(combination2);
P(mutex);
拿走组合2
v(mutex);
}
}
}
smoker3(){
while(1){
if(i==2){
P(left);
P(combination3);
P(mutex);
拿走组合3
v(mutex);
}
}
}
4.读者写者问题
问题描述:
有读者(reader)和写者(writer)两组并发进程,共享一个文件,当两个或以上的读进程同时访问共享数据时不会产生副作用,但若某个写进程和其他进程(读进程或写进程)同时访问共享数据时则可能导致数据不一致的错误。
因此要求:
① 允许多个读进程可以同时对文件执行读操作;
② 只允许一个写进程往文件中写信息;
③ 任一写进程在完成写操作之前不允许其它读进程或写进程工作;
④ 写进程执行写操作前,应让已有的读进程和写进程全部退出。
关系分析:
读进程和读进程可以同时进行(最关键的点)
读进程和写进程是互斥的关系
写进程和写进程也是互斥的关系
起始思路:
semaphore rw=0; //表示当前是否有进程在访问共享文件
int count=0; //记录当前有几个读进程在访问文件
writer(){
while(1){
P(rw);
写文件
V(rw);
}
}
reader(){
while(1){
if(count==0){
P(rw);
}
count++;
读文件
count--;
if(count==0){
V(rw);
}
}
}
这时发现一个问题,就是reader
中如果第一个两个读进程并发执行,则可能第一个进程执行P(rw)
,在count
还没有加一的时候,切换到第二个读进程,此时count=0
从而,第二个进程访问时出现阻塞的情况
对于这种问题,我们发现他是因为检查和count
的赋值不能同时进行
所以想到了一种改进方法:
semaphore mutex=1; //用于保证对count变量的互斥访问
semaphore rw=0; //表示当前是否有进程在访问共享文件
int count=0; //记录当前有几个读进程在访问文件
writer(){
while(1){
P(rw);
写文件
V(rw);
}
}
reader(){
while(1){
P(mutex); //改进的地方
if(count==0){
P(rw);
}
count++;
V(mutex); //改进的地方
读文件
P(mutex); //改进的地方
count--;
if(count==0){
V(rw);
}
V(mutex); //改进的地方
}
}
而上述程序又出现一个问题,即只要count!=0
,写进程就一直无法开始写,
容易出现饥饿的现象,解决的办法就是再加一个“写优先 ”:
semaphore w=1; //用于实现“写优先”
semaphore mutex=1; //用于保证对count变量的互斥访问
semaphore rw=0; //表示当前是否有进程在访问共享文件
int count=0; //记录当前有几个读进程在访问文件
writer(){
while(1){
P(w); //修改
P(rw);
写文件
V(rw);
V(w); //修改
}
}
reader(){
while(1){
P(w); //修改
P(mutex);
if(count==0){
P(rw);
}
count++;
V(mutex);
V(w); //修改
读文件
P(mutex); //改进的地方
count--;
if(count==0){
V(rw);
}
V(mutex); //改进的地方
}
}
上面这种方法称为读写公平法,即如果出现
读者1->写者1->读者2
这种情况时,不会再像倒数第二种算法一样读者2优先于写者1执行的情况了
总结:
读者写者问题的思路
1)核心思想就是设置了一个计数器count,用count的值来判断当前进入的值是第一个还是最后一个读进程,从而进行不同的PV操作
2)count变量的检查和赋值不能一气呵成,所以想到了使用互斥信号量
3)认真体会如何解决写进程饥饿的问题的
5.哲学家问题
有5个哲学家共用一张圆桌,分别坐在周围的5张椅子上,在圆桌上有5个碗和5只筷子,他们的生活方式是交替地进行思考和进餐。平时,每个哲学家进行思考,饥饿时便试图拿起其左右最靠近他的筷子,只有在他拿到两只筷子时才能进餐。进餐完毕,放下筷子继续思考。
起始思路:
semaphore chopstick[5]={1,1,1,1,1};
Pi{ //i号哲学家的进程
while(1){
P(chopstick[i]); //拿左边的筷子
P(chopstick[(i+1)%5]); //拿右边的筷子
吃饭
V(chopstick[i]); //放左边的筷子
V(chopstick[(i+1)%5]); //放右边的筷子
思考
}
}
这种解决办法有弊端,就是当每个哲学家都拿起自己左边的筷子时,每个哲学家都只有一只筷子,无法就餐,每个哲学家都在等待身边的哲学家放下自己手里的筷子,所以出现了死锁的现象
为了防止死锁:
方案一:对哲学家进程施加一定的限制 条件,比如最多允许四个哲学家同时进餐
伪代码:
semaphore chopstick[5]={1,1,1,1,1};
int count=0;
Pi{ //i号哲学家的进程
while(1){
if(count<5){
P(chopstick[i]); //拿左边的筷子
P(chopstick[(i+1)%5]); //拿右边的筷子
吃饭
V(chopstick[i]); //放左边的筷子
V(chopstick[(i+1)%5]); //放右边的筷子
count++;
}
思考
}
}
方案二:要求奇数号哲学家先拿左边的筷子,再拿右边的筷子,与偶数号哲学家正好相反
伪代码:
semaphore chopstick[5]={1,1,1,1,1};
Pi{ //i号哲学家的进程
while(1){
if(i%2!=0){
P(chopstick[i]); //拿左边的筷子
P(chopstick[(i+1)%5]); //拿右边的筷子
吃饭
V(chopstick[i]); //放左边的筷子
V(chopstick[(i+1)%5]); //放右边的筷子
思考
}
if(i%2==0){
P(chopstick[(i+1)%5]); //拿右边的筷子
P(chopstick[i]); //拿左边的筷子
吃饭
V(chopstick[(i+1)%5]); //放右边的筷子
V(chopstick[i]); //放左边的筷子
思考
}
}
}
方案3(代码最简单,思维最复杂):仅当一个哲学家左右两只筷子都可以使用时,才允许他抓起筷子
方法:互斥的拿筷子,设置一个互斥信号量
源代码:
semaphore chopstick[5]={1,1,1,1,1};
semaphore mutex=1;
Pi{ //i号哲学家的进程
while(1){
P(mutex);
P(chopstick[i]); //拿左边的筷子
P(chopstick[(i+1)%5]); //拿右边的筷子
V(mutex);
吃饭
V(chopstick[i]); //放左边的筷子
V(chopstick[(i+1)%5]); //放右边的筷子
思考
}
}
2.3.5 管程
1.为什么要引入管程
由于信号量机制存在编写复杂,易出错的情况,
2.管程的定义和基本特征
管程的组成:
1)管程的名称
2)管程内部的数据结构说明
3)对数据结构初始化的语句
4)对数据结构进行操作的一组函数
管程的特殊之处(个人理解):
1)管程把许多操作都封装起来,对外只提供一个大的函数,就像输入,输出一样
2)每次仅允许一个进程进入管程,从而实现进程互斥
标签:count,同步,管程,chopstick,进程,mutex,缓冲区,semaphore,2.3 来源: https://blog.csdn.net/be_stronger/article/details/113914794
本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享; 2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关; 3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关; 4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除; 5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。