1、生产者消费者问题
①系统中有一组生产者进程和一组消费者进程,生产者进程每次生产一个产品放入缓冲区,消费者进程每次从缓冲区中取出一个产品并使用。【注:这里的“产品”理解为某种数据】
生产者、消费者共享一个初始为空、大小为n
的缓冲区。
②只有缓冲区没满时,生产者才能把产品放入缓冲区,否则必须等待
【当缓冲区满时,生产者需要等待消费者取走产品才能继续操作,有顺序,所以是同步关系】
③只有缓冲区不空时,消费者才能从中取出产品,否则必须等待【同理也是同步关系】。
④缓冲区是临界资源,各进程必须互斥地访问
PV
操作题目分析步骤:
- 关系分析:找出题目中描述的各个进程,分析它们之间的同步、互斥关系。
- 整理思路:根据各进程的操作流程确定
P、V
操作的大致顺序。 - 设置信号量:设置需要的信号量,并根据题目条件确定信号量初值。【互斥信号量初值一般为
1
,同步信号量的初始值要看对应资源的初始值是多少】
semaphore mutex=1; //互斥信号量,实现对缓冲区的互斥访问
semphore empty=n; //同步信号量,表示空闲缓冲区的数量
semphore full=0; //同步信号了,表示产品的数量,也叫非空缓冲区的数量
producer(){
while(1){
生产一个产品;
P(empty); //消耗一个空闲缓冲区
P(mutex); //准备进入互斥,通过后禁止其他进程访问
把产品放入缓冲区;
V(mutex); //mutex=1,资源使用完毕,允许其他进程访问
V(full); //经过生产,产品+1
把产品放入缓冲区;
}
}
consumer(){
while(1){
P(full); //消耗一个产品(非缓冲区)
P(mutex); //准备进入互斥,通过后禁止其他进程访问
从缓冲区取出一个产品;
V(mutex); //解锁
V(empty); //增加一个空闲缓冲区
}
}
能否改变相邻P操作顺序?【先执行
P(mutex)
再执行P(empty/full)
】答:不能。若先执行
P(mutex)
,则会进入下一步并锁程序,但若此时是满的,则p(empty)
无法执行【空时同理p(full)
】,即会卡在此处,一直占用临界资源无法释放,后续进程也就无法对其进行调用,从而造成混乱。
2、读者写者问题
有读者和写者两组并发进程,共享一个文件,当两个或两个以上的读进程同时访问共享数据时不会产生副作用,但若某个写进程和其他进程(读进程或写进程)同时访问共享数据时则可能导致数据不一致的错误。因此要求:
①允许多个读者可以同时对文件执行读操作
②只允许一个写者往文件中写信息
③任一写者在完成写操作之前不允许其他读者或写者工作
④写者执行写操作前,应让已有的读者和写者全部退出
为什么读进程可以并发,写进程一次却只允许一个?
答:因为读数据后不会将数据清空,读与读之间互不影响,因此可以并发;而写与写如果同时发生,后者写的内容会将前者覆盖,从而造成混乱;写和读同时进行时,可能造成读写内容不一致,造成混乱。
关系:写读互斥、写写互斥、读读不互斥
semaphroe rw=1; //用于实现对文件的互斥访问
int count=0; //记录当前有几个读进程在访问文件
semaphore mutex=1; //用于保证对count变量的互斥访问
semaphore w=1; //用于实现相对"写优先"
writer(){
while(1){
P(w); //阻止读进程进入
P(rw); //互斥访问临界资源
写文件...
V(rw);
V(w);
}
}
reader(){
while(1){
P(w); //配合写进程,防止写进程饥饿
P(mutex); //一次只允许一个读进程进行count操作。防止混乱
if(count==0)
P(rw); //加锁,此时写进程无法访问
count++;
V(mutex);
V(w);
读文件...
P(mutex); //防止读进程结束的count--同时进行,从而造成V(mutex)无法进行
count--;
if(count==0)
V(rw);
V(mutex);
}
}
读者①–>读者②–>写者③–>读者④
- 进入
reader(),P(w)
加锁,P(mutex)
加锁【在释放前其他读进程无法进入】,此时count==0
,所以P(rw)
加锁【读写互斥】,count++
后为1
,表示此时有1
个元素在访问文件,++
操作结束后V(mutex)、V(w)
解锁,进入读文件……此时还没有释放P(rw)
。 - 进入
reader()
,由于P(w)
和P(mutex)
都解锁,因此可直接进入并加锁,同时因为count
为1
,所以不会进入P(rw)
,因此不会被没有释放P(rw)
而影响到,可以正常进行,count++
后为2
,解锁,进入读文件……假设步骤②
速度比步骤①
快,进入P(mutex)
加锁,使进行count--
时只有1
个读进程在操作,避免混乱,执行后count
为1
,不满足解锁rw
条件,因此步骤②将资源归还后P(rw)依旧没有被释放,即依然有读进程在执行,写进程无法进入 - 写文件进入
P(w)
并为其加锁,P(rw)
会因为读进程没有释放而无法进入,因此需要等读进程结束后释放出资源才能继续往下执行,从而实现读写互斥。假设此时前边的读进程都已释放,读进程进入写文件,P(rw)
加锁…… - 在
P(w)
处无法进入,因为步骤③
为其加锁且还未释放,必须等步骤③
释放后才可进入,从而实现进程"先来先服务"原则,假设步骤③
执行完毕,则步骤④
变成一个单独的读进程,正常加锁解锁即可。
3、哲学家吃饭问题
一张圆桌上坐着
5
名哲学家,每两个哲学家之间的桌上摆1
根筷子,桌子的中间是1
碗米饭。哲学家们倾注毕生的精力用于思考和进餐,哲学家在思考时,并不影响他人。只有当哲学家饥饿时,才试图拿起左、右两根筷子【一根一根地拿起】。如果筷子已在他人手上,则需等待。饥饿的哲学家只有同时拿起两根筷子才可以开始进餐,当进餐完毕后,放下筷子继续思考。
思路分析:
要防止死锁发生,即每人拿起1
根筷子
- 最多允许
4
个哲学家同时进餐,则最少有1
个哲学家是可以吃饭的【原先每人1
根正好分配,现在有1
个人不拿筷子,因此肯定有1
个人有两根筷子】 - 要求奇数号吃饭先拿起左边筷子;偶数号吃饭先拿起右边筷子,这样当相邻两位哲学家想吃饭时,
1
位会成功,另1
位则被阻塞,避免了占有1
只筷子后等待另1
只筷子的情况。 - 仅当左右都有筷子时才允许他抓起筷子。
//以下为拿筷子时先加锁,两根筷子都到手才解锁的实现方式
semaphore chopstick[5]={1,1,1,1,1}; //初始筷子状态
semaphore mutex=1; //互斥取筷子
Pi(){
while(1){
P(mutex); //互斥加锁
P(chopstick[i]); //拿左边
P(chopstick[(i+1)%5]); //拿右边
V(mutex); //拿完筷子解锁
吃饭...
V(chopstick[i]); //吃完放左边筷子
V(chopstick[(i+1)%5]); //放右边
哲学家继续思考...
}
}