最近在准备推免的面试把王道的进程这一章拿出来做了一下,收获挺多的,写个文章总结下
2.3进程同步
访问临界资源过程
do{
entry section;//进入区 设置访问临界区标志
critical section;//临界区 访问临界资源
exit section;//退出区 将访问临界区标志清除
remainder section;//剩余区
}while(true)
软件实现临界区互斥
1.单标志法
单标志法主要问题是对临界区资源的访问各个进程需要交替进行,如果一个进程完成后,及时临界区可以访问,另一个进程也无法进入。这就违背了“空闲让进”原则
P0进程:
while(turn!=0);
critical section;
turn=1;
remainder section;
p1进程:
while(turn!=1);
critical section;
turn =0;
remainder section;
2.双标志法先检查
先判断对方是否在访问临界区,然后再决定自己是否进入,它都问题是如果按照1 2 3 4的顺序执行的话,双法都进入临界区,这样就违背了忙则等待的原则
pi进程
while(flag[j));//1
flag[i]=TRUE;//3
critical section;
flag[i]=FALSE;
remainder section;
pj进程
while(flag[i]);//2
flag[j]=TRUE;//4
critical section;
flag[j]=FALSE;
remainder section;
3.双标志后检查法
先将临界区设置为自己的,再判断对方是否访问,这个方法的问题是如果按照1 2 3 4 执行的话,3,4 都会永远处于真,这样会导致饥饿问题
pi进程
flag[i]=TRUE;//1
while(flag[j]);//3
critical section;
flag[i]=FALSE;
remainder section;
pj进程
flag[j]=TRUE;//2
while(flag[i]);//4
critical section;
flag[j]=FALSE:
remainder section;
4.Peterson‘s Algorithm
该算法的核心是通过增加turn让后执行的turn赋值语句,决定谁先进入临界区
pi进程
flag[i]=TRUE;//1
turn=j;
while(flag[j]&&turn==j);//3
critical section;
flag[i]=FALSE;
remainder section;
pj进程
flag[j]=TRUE;//2
turn=i;
while(flag[i]&&turn==i);//4
critical section;
flag[j]=FALSE:
remainder section;
硬件实现方法
1.中断屏蔽方法
因为cpu只有在发生中断的时候才能进行进程切换,因此关中断就可以保证临界区资源顺利访问
2.硬件指令
TestAndSet指令
Swap 指令
优点:适用于任意数目的进程,不管是单处理机还是多处理机:简单,容易验证其正确性,可以支持进程内有多个临界区,只需为没给临界区设立一个信号量即可。
缺点:进程等待进入临界区时需要消耗处理机时间,不能实现让权等待,从等待进入进程中随机选择一个进入临界区,有的进程可能一直选不上,从而导致”饥饿“现象。
信号量
信号量只能被两个标准的原语wait(S)和signal(S)来访问,也可被记错P/V
1.整型信号量
一个用于表示资源数目的整型量S,wait和signal操作可描述为下方,问题是wait中如果S<=0,就会不断尝试,因此该机制并未遵循“让权等待”原则
wait(S){
while(S<=0);
S=S-1;
}
signal(S){
S=S+1;
}
2.记录型信号量
记录型信号量通过一个记录进程的队列来避免重复尝试。
记录型信号量数据结构
typedef struct{
int value;
struct process *L;
}semaphore;
void wait(semaphore S){
S.value--;
if(S.value<0){
block(S.L);
}
}
void signal(semaphore S)
S.value++;
if(S.value<=0){
wakeup(S.L);
}
}
重要概念
1.临界资源:一次仅允许一个进程使用的资源。
2.临界区:进程中用于访问共享资源的代码。
3.临界资源是互斥共享资源,非共享数据不属于临界资源。
4.可重入代码(Reentry code)也叫纯代码(Pure code)是一种允许多个进程同时访问的代码。为了使各进程所执行的代码完全相同,故不允许任何进程对其进行修改
5.公用队列可供多个进程使用,但一次只可有一个进程使用。
6.P,V 操作是一种低级的进程通信原语,它是不能被中断的,也不是系统调用的。
7.执行P操作可能导致进程阻塞,但是进程在执行这个操作时还是执行状态。
8.用P,V操作实现进程同步,信号量的初值由用户确定。若期望的消息尚未产生,则置初值为 0,若期望的消息已经存在,则置为条件中的数值,比如读者写者问题,empty因为已经存在置为n,full开始时不存在就置为0。
9.管程不仅能实现进程间的互斥,而且能够实现进程间的同步。
10.原语是指完成某种功能且不被分割不被中断执行的操作序列,通常可由硬件来实现完成不被分割执行特性的功能。在单处理机时还可以通过软件关闭中断的方式。
问题
1.信号量的初始值问题?
信号量有两种资源信号量和互斥信号量,同步信号量初始值为1,小于0时,其绝对值为在等待进入该临界区的进程数。资源信号量初始值为该临界资源的数目,小于0时,其绝对值为在等待该临界资源的进程数,大于0时,其绝对值为该临界资源的剩余数。
2. 管程wait/signal与PV操作
信号量的P操作可能阻塞,也可能不阻塞;而管程的wait操作一定会阻塞。信号量的V操作如果唤醒了其他线程,当前线程与被唤醒线程并发执行;对于管程的signal操作,要么当前线程继续执行,要么被唤醒线程继续执行,二者不能并发。 同时信号量的V操作一定会改变信号量S的值(S=S+1)。管程中的signal操作是针对某个变量的,如果不存在因该条件而阻塞的进程,signal不会产生任何影响。
信号量的利用
1.信号量的同步
要求P1执行X语句后才可执行P2 Y语句,通过在将S=0,和在Y语句上方放置P操作,来限制P2执行,通过在P1 X语句后放置V操作来使P2可以继续执行,见代码。
semaphore S=0;
P1(){
x;
V(S);
}
P2(){
P(S);
y;
}
2.信号量的互斥
通过将S=1和在对临界区资源上方放置P操作来实现,因为一旦有个进程实现了P操作别的进程就P操作就会组织他们进入临界区只能等第一个进程的V操作
semaphore S=1;
P1(){
P(S);
临界区;
V(S);
}
P2(){
P(S);
临界区;
V(S);
}
3 信号量的前驱图(同步的加强版)
只需根据每个节点的出度入度即可得知P/V操作的数量,同时每条边都是一个同步关系需要设置一个信号量,出度是V操作,入度是P操作,参考案例
S1->S2 a1
S1->S3 a2
S2->S4 b1
S2->S5 b2
S3->S6 c
S4->S6 d
S5->S6 e
semaphore a1=a2=b1=b2=c=d=e=0;
S1(){
...;
V(a1);V(a2);
}
S2(){
P(a1);
...;
V(b1);V(b2);
}
S3(){
P(a2);
...;
V(c);
}
S4(){
P(b1);
...;
V(d);
}
S5(){
P(b2);
...;
V(e);
}
S6(){
P(c);
P(d);
P(e);
...;
}
学习了信号量的利用让我们将他们融汇贯通吧
经典同步问题
1.生成者消费者问题
问题描述
一组生产者进程和一个消费者进程共享一个初始为空,大小为n的缓冲区,只有缓冲区没满时,生产者才能把消息放入到缓冲区,否则必须等待;只有缓冲区不空时,消费者才能从中取出消息,否则必须等待。由于缓冲区是临界资源,它只允许一个生产者放入消息,或者一个消费者从中取出消息。
关系分析:生产者于消费者因为有先后顺序,所以是同步关系,因为对缓冲区的争夺所以也是互斥关系。
思路:设置一个mutex=1来实现对缓冲区的互斥反问,要知道缓冲区对于生产者的资源是空多少,他的初始值是empty=n,缓冲区对消费者的资源是满多少,顾设置full=0;
semaphore mutex=1;
semaphore empty=n;
semaphore full=0;
producer(){
while(True){
P(empty)
P(mutex)
put();
V(mutex)
V(full);
}
}
consumer(){
while(True){
P(full)
P(mutex)
put();
V(mutex)
V(empty);
}
}
注解:有的人可能会问为什么mutex要在full或者empty的里面,可以这么理解,他们各自都必须先确定自己可以对缓冲区操作才能占用缓冲区,否则会出现死锁,比如一开始对消费者如果mutex先锁定缓冲区,然后p(full)发现缓冲区为空被阻塞,但因为他对缓冲区的锁定使得生成者也没有机会进入,导致死锁。
生产者-消费者变种题1
问题描述:桌子上有一只盘子,每次只能向其中放入一个水果,爸爸专门向盘子中放入苹果,妈妈专门向盘子中放入橘子,儿子专等吃盘子中的橘子,女儿专等吃盘子中的苹果。只有盘子为空时,爸爸或妈妈就可向盘子中放一个苹果,仅当盘子中的有自己需要的水果时,儿子或女儿可以从盘子中取出。
关系分析:爸爸妈妈之间对盘子是互斥的,爸爸女儿和妈妈儿子是同步的
思路分析:设置mutex=1互斥访问盘子,设置emptyA=1,fullA=0,同步爸爸女儿,设置emptyB=1,fullB=0,同步妈妈儿子。
semaphore mutex=1;
semaphore emptyA=1,fullA=0;
semaphore emptyB=1,fullB=0;
dad(){
while(True){
P(emptyA)
P(mutex)
putApple();
V(fullA);
}
}
Mom(){
while(True){
P(emptyB);
P(mutex);
putJuice();
V(fullB);
}
}
daughter(){
while(True){
P(fullA);
getApple();
V(mutex);
V(emptyA);
}
}
son(){
while(True){
P(fullB);
getJuice();
V(mutex);
V(emptyB);
}
}
上方为标准解法但是仔细想想是可以简化的,因为mutex等价于empty,因为如果获得盘子权力,那必是儿子,女儿已经拿掉了,通过V操作唤醒的,那盘子必定是空,可以放入无需检查,因此去掉两个empty仍然正确。
semaphore mutex=1;
semaphore emptyA=1,fullA=0;
semaphore emptyB=1,fullB=0;
dad(){
while(True){
P(mutex)
putApple();
V(fullA);
}
}
Mom(){
while(True){
P(mutex);
putJuice();
V(fullB);
}
}
daughter(){
while(True){
P(fullA);
getApple();
V(mutex);
}
}
son(){
while(True){
P(fullB);
getJuice();
V(mutex);
}
}
生产者-消费者变种问题2
(2009年计算机联考真题)三个进程P1,P2,P3互斥使用一个包含N个单元的缓冲区,P1每次用produce()生成一个正整数并用put()送入缓冲区某一空单元中;P2每次用getodd()从该缓冲区中取出一个奇数并用countodd()统计奇数个数;P3每次用geteven()从该缓冲区中取出一个偶数并使用counteven()统计偶数个数,请使用信号量机制实现三个进程的同步与互斥活动,并说明所定义的信号量的含义。
关系分析:P1与P2/P3是同步,他们之间因为对缓冲区的争夺是互斥
思路分析:设置mutex=1实现对缓冲区的互斥访问,设置empty=n和full=0来实现同步。
semaphore mutex=1;
semaphore empty=n;
semaphore pA=0;
semaphore pB=0;
P1(){
while(True){
P(empty);
x=produce();
P(mutex);
put();
V(mutex);
if(x%2==0){
V(pB)
}else{
V(pA);
}
}
}
P2(){
while(True){
P(pA);
P(mutex);
getodd();
V(mutex);
V(empty)
countodd();
}
}
P3(){
while(True){
P(pB);
P(mutex);
geteven();
V(mutex);
V(empty);
counteven();
}
}
生产者-消费者变种问题3
在一个仓库中可以存放A和B两种产品,要求:
(1)每次只能存入一种产品。
(2)A产品数量-B产品数量 (3) B产品数量-A产品数量 其中M,N是正整数,试用P操作,V操作描述产品A与产品B的入库过程。 思路分析:A最多能生产的数量肯定是M-1,B最多能生产的数量最多肯定是N-1,同时A生成 生产者-消费者变种问题4 某个工厂有两个生产车间和一个装配车间,两个生产车间分别生产A,B两种零件,装配车间的任务是把A,B两种零件组装成产品。两个生产车间每生产一个零件后都要分别把它们送到装配车间F1,F2上,F1存放零件A,F2存放零件B,F1和F2的容量均可以存放10个零件。装配工人每次从货架上取出一个零件A和一个零件B后组装成产品。请用P,V操作进行正确管理 关系分析:装配车间为消费者,生产A,B两种零件的是车间是生产者。 思路分析:这道题唯一不同就是多了一个组装,骑士只要把获取两种产品的过程写在一起就是组装了,mutexA=1,mutexB=1,对车间的互斥访问,emptyA=10,emptyB=10,fullA=0,fullB=0。 生产者消费者变种问题5 某寺庙,有小和尚,老和尚若干,有一水缸,由小和尚提入水缸供老和尚饮用,水缸可容纳10桶水,水取自同一井中,水井径窄,每次只能容纳一桶取水,水桶总数为3个,每次入缸取水仅为1桶水,且不可同时进行。试给出有关从缸取水,入水的算法描述。 关系分析:小和尚和老和尚之间喝水为同步,同时对水缸为互斥,对桶进行争夺,小和尚之间对井的争夺也是互斥 思路分析:井互斥mutexJ=1;缸互斥mutexG=1,桶资源mutex=3;full=0,empty=10; 生产者消费者变种问题6 设P,Q,R共享一个缓冲区(该缓冲区大小为1),P,Q构成一对生产者-消费者,R既为生产者又为消费者,使用P,V操作实现其同步。 思路分析:R既为消费者又为生产者,则必须在执行前判断状态,若empty=1,则执行生产者功能,若full==1,执行消费者功能 生产者消费者变种问题7 设自行车生产线上有一只箱子,其中有N个位置(N>=3),若每个位置可存放一个车架或一个车轮,又设有三个工人,其活动分别为: 工人1活动: do{ 加工一个车架; 车架放入箱中; }while(1) 工人2活动: do{ 加工一个车轮; 车架放入箱中; }while(1) 工人3活动: do{ 箱中取出一个车架; 箱中取二个车轮; 组装为一台车 }while(1) 试分别用信号量宇PV操作实现三个工人的合作,要求解中不含死锁 思路分析:本体需要知道一个限制虽然成产线有N个位置但是车轮的最大数目却是N-1,车架是N-2,否则会出现死锁。同时需要设置一个mutex=1来互斥访问缓冲区,设置 注释:本体没有强制要求去拿互斥,可以去掉mutex 生产者消费者变种问题8 (2015年计算机联考题)有A,B两个人通过信箱进行辩论,每个人都从自己的信箱中国呢取得对方的问题。将答案和向对方提出的新问题组成一个邮件放入对方的邮箱中。假设A的信箱最多放M个邮件,B的信箱最多放N个邮件,初始时A的信箱中有x个邮箱(0 CoBegin A{ while(TRUE){ 从A的信箱中取出一个邮件; 回答问题并提出一个新问题; 将新邮件放入B的信箱; } } B{ while(TRUE){ 从B的信箱中取出一个邮件; 回答问题并提出一个新问题; 将新邮件放入A的信箱; } } 当信箱不为空时,辩论者才能从信箱中取邮件,否则等待,当信箱不满时,辩论者才能将新邮件放入信箱,否则等待。请添加必要的信号量和P,V(或wait,signal)操作,以实现上述过程的同步,要求写出完整步骤。 思路分析:本题是个双向生产者消费者问题,同时要求每次要将获得的东西整理后重新发给对方。 semaphore AB=M-1;
semaphore BA=N-1;
semaphore mutex=1;
P1(){
while(True){
P(AB)
P(mutex)
A();
V(mutex)
V(BA);
}
}
P2(){
while(True){
P(BA)
P(mutex)
B();
V(mutex)
V(AB);
}
}
semaphore emptyA=10,emptyB=10;
semaphore fullA=0,fullB=0;
semaphore mutexA=1,mutexB=1;
PA(){
while(True){
P(emptyA);
P(mutexA);
putA();
V(mutexA);
V(fullA);
}
}
PB(){
while(True){
P(emptyB);
P(mutexB);
putB();
V(mutexB);
V(fullB);
}
}
PC(){
while(True){
P(fullA);
P(mutexA);
getA();
V(mutexA);
V(emptyA);
P(fullB);
P(mutexB);
getB();
V(mutexB);
V(emptyB);
//组装
}
}
semaphore mutexJ=1,mutexG=1,Tong=3,full=0,empty=10;
Psmall(){
while(True){
P(empty);
P(Tong)
P(mutexJ);
//取水
P(mutexJ);
P(mutexG);
put();
V(mutexG);
V(full);
V(Tong);
}
}
Podd(){
while(True){
P(full);
P(Tong);
P(mutexG);
get();
V(mutexG);
V(empty);
//喝水
V(Tong);
}
}
semaphore full=0;
semaphore empty=1;
semaphore mutex=1;
Procedure P
{
while(TRUE){
P(empty);
P(mutex);
put();
V(mutex);
V(full);
}
}
Procedure Q
{
while(TRUE){
P(full);
P(mutex);
get();
V(mutex);
V(empty);
}
}
Procedure R
{
while(TRUE){
if(empty==1){
P(empty);
P(mutex);
put();
V(mutex);
V(full);
}
if(full==1){
P(full);
P(mutex);
get();
V(mutex);
V(empty);
}
}
semaphore mutex=1;
semaphore empty1=N-2;
semaphore full1=0;
semaphore empty2=N-1;
semaphore full2=0;
semaphore empty=N;
do{
P(empty1);
P(empty);
加工一个车架;
P(mutex);
车架放入箱中;
V(mutex);
V(full1);
}while(1)
工人2活动:
do{
P(empty2);
P(empty);
加工一个车轮;
P(mutex);
车轮放入箱中;
V(mutex);
V(full2);
}while(1)
工人3活动:
do{
P(full1);
P(mutex);
箱中取出一个车架;
V(mutex);
V(empty);
V(empty1);
P(full2);
P(full2);
P(mutex);
箱中取一个车轮;
V(mutex);
V(empty);
V(empty);
V(empty2);
V(empty2);
组装为一台车
}while(1)
semaphore empty1=N-x,full1=x;
semaphore empty2=M-y,full2=y;
semaphore mutex1=1,mutex2=1;
A{
while(TRUE){
V(full1);
P(mutex1);
从A的信箱中取出一个邮件;
V(mutex1);
P(empty2);
回答问题并提出一个新问题;
P(mutex2);
将新邮件放入B的信箱;
V(mutex2);
}
}
B{
while(TRUE){
V(full2);
P(mutex2);
从B的信箱中取出一个邮件;
V(mutex2);
P(empty1);
回答问题并提出一个新问题;
P(mutex1);
将新邮件放入A的信箱;
V(mutex1);
}
}