一、5位哲学家就餐问题
semaphore fork[5]; for (int i=0;i<5;i++) fork[i]=1; cobegin process philosopher_i( ) { //i= 0,1,2,3,4 while(true) { think( ); P(fork[i]); //先取右手的叉子 P(fork[(i+1)%5]); //再取左手的叉子 eat( ); V(fork[i]); V(fork[(i+1)%5]); } } coend
这个解法会出现死锁!
为了避免死锁
1.至多允许四个哲学家同时取叉子
2.奇数号先取左手边的叉子,偶数号先取右手边的叉子
3.每个哲学家取到手边的两把叉子才吃,否则一把叉子也不取
semaphore fork[5]; for (int i=0;i<5;i++) fork[i]= 1; semaphore room=4; //增加一个侍者 cobegin process philosopher_i( ){/*i=0,1,2,3 */ while(true) { think( ); P(room); //控制最多允许4为哲学家取叉子 P(fork[i]; P(fork[(i+1)%5] ) ; eat( ); V(fork[i]); V(fork([i+ 1] % 5); V(room); } } coend
void philosopher (int i) { if i mod 2==0 then { P(fork[i]); //偶数哲学家先右手 P(fork[(i+1) mod 5 ]); //后左手 eat(); V(fork[i]); V (fork[(i+1) mod 5]); } else { P (fork[(i+1) mod 5 ]); //奇数哲学家,先左手 P (fork[i]); //后右手 eat(); V(fork[(i+1) mod 5]); V(fork[i]); } }
semaphore fork[5]; for (int i=0;i<5;i++) fork[i]= 1; cobegin process philosopher_i( ){/*i=0,1,2,3 */ while(true) { think( ); P(fork[i];//先取右手的叉子 /*i=4,P(fork[0])*/ P(fork[(i+1)%5] ) ; //再取左手的叉子 /*i=4,P(fork[4])*/ eat( ); V(fork[i]); V(fork([i+ 1] % 5); } } coend
通过Hoare管程求解哲学家问题
详解:
type dining_philosophers=monitor int self_count[5]; InterfaceModule IM; for (int i=0;i<5;i++) //初始化,i为进程号 state[i]=thinking; define pickup,putdown; use enter,leave,wait,signal; void pickup(int i) { //i=0,1,...,4 enter(IM); state[i]=hungry; test(i); if(state[i]!=eating) wait(self[i],self_count[i],IM); leave(IM); } void putdown(int i) { //i=0,1,2,..,4 enter(IM); state[i]=thinking; test((i-1)%5); test((i+1)%5); leave(IM); } void test(int k) { //k=0,1,...,4 if((state[(k-1)%5]!=eating)&&(state[k]==hungry) &&(state[(k+1)%5]!=eating)) { state[k]=eating; signal(self[k],self_count[k],IM); } } } 任一个哲学家想吃通心面时调用过程pickup,吃完通心面之后调用过程putdown。 cobegin process philosopher_i( ) { //i=0,…,4 while(true) { thinking( ); dining_philosophers.pickup(i); eating( ); dining_philosophers.putdown(i); } } coend
(A) 假设一开始3号哲学家,第一个做pickup,他会很顺畅,在pickup中通过enter(IM)进入管程,修改自身状态state[3]=hungry, 接着test(3)中if条件通过,修改自身状态state[3]=eating, 因为这时候没有进程(哲学家)阻塞在self[3]上,即self_count[3]==0,对照signal(self[3], self_count[3], IM)中的PV实现,实际上此时signal什么也不做。然后,判断if (state[3]!=eating)不成立,因此wait操作也不做。再然后,3号哲学家执行leave(IM)退出管程,以便让其他哲学家进入管程。
分析:实际上这里3号哲学家成功的取到他需要的两个叉子。他通过将自己的状态改为eating,对于i=2号哲学家在取叉子的时需要做test(i),state[i+1]!=eating不成立;对于i=4号哲学家在取叉子的时需要做test(i),state[i-1]!=eating不成立;从而封堵其左右哲学家转入eating状态,2、4号哲学家不能成功转入eating状态,将执行wait操作,也即在3号哲学家没有执行putdown中的test((i-1)%5)和test((i+1)%5)之前,左右哲学家不能成功取到叉子。
(B) 接着,在3号哲学家还没有putdown之前,如果1号哲学家执行pickup,他也会很顺畅,与之前的3号哲学家pickup时的情形类似,在pickup中通过enter(IM)进入管程,修改自身状态state[1]=hungry, 接着test(1)中if条件通过,修改自身状态state[1]=eating, 因为这时候没有进程(哲学家)阻塞在self[1]上,即self_count[1]==0,对照signal(self[1], self_count[1], IM)中的PV实现,实际上此时signal什么也不做。然后,判断if (state[1]!=eating)不成立,因此wait操作也不做。再然后,3号哲学家执行leave(IM)退出管程,以便让其他哲学家进入管程。
分析:实际上这里1号哲学家成功的取到他需要的两个叉子。他通过将自己的状态改为eating,从而封堵其左右哲学家转入eating状态,0、2号哲学家不能成功转入eating状态,也即在1号哲学家没有执行putdown中的test((i-1)%5)和test((i+1)%5)之前,不能成功取到叉子。
(C) 接着,在1和3号哲学家没有putdown之前,此时,state[1]==eating,state[3]==eating。如果2号哲学家执行pickup,他会很不顺畅,在pickup中通过enter(IM)进入管程,修改自身状态state[2]=hungry,接着test(2)中的if条件state[1]!=eating,state[3]!=eating都不成立,因此2号哲学家没有成功把自身状态修改为eating,也不用做test(2)中signal(self[2], self_count[2], IM)操作,再接着判断if (state[2]!=eating)成立,因此做wait(self[2], self_count[2], IM)操作,对照wait操作的PV实现,此时,self[2]_count++表示在self[2]等待的进程数加1,然后判断if (IM.next_count>0),如果此时1和3号进程都没有执行putdown中的signal操作,那么该条件不成立,然后执行V(IM.mutex)退出管程,接着P(self[2])阻塞自己,等待1和3号哲学家执行putdown中的signal操作唤醒之。
(D) 接着,假设1号哲学家吃完,执行putdown,在putdown中通过enter(IM)进入管程,并修改自身状态state[1]=thinking,然后test((i-1)%5),即test(0),其中state[(0-1)%5]即state[4]!=eating成立,state[0]==hungry不成立(表示0号哲学家没有执行pickup),state[(0+1)%5]即state[1]!=eating成立,即整个if条件不成立,if下的语句不做。然后test((i+1)%5),即test(2),其中state[(2-1)%5]即state[1]!=eating成立,state[2]==hungry(表示之前2号哲学家已经执行了pickup,且没有成功取到叉子),state[(2+1)%5]即state[3]!=eating不成立,即整个if条件还是不成立,然后1号哲学家执行leave(IM)退出管程,以便其他哲学家进入管程。
分析: 1号哲学在putdown的时候执行test(0)和test(2),test(0)用意在于唤醒右邻居0号哲学家,但是0号哲学家还没有执行pickup,也就没有把自身状态修改为hungry,1号哲学家的这份好心浪费了;接着test(2)用意在于唤醒左邻居2号哲学家,但是2号哲学家成功取到叉子之前要判断3号是否eating,实际上此时3号依然eating,因此test(2)的用意没有真正成功。看来还真不简单,不要紧,继续往下看。
(E) 当1号哲学家在putdown中退出管程,此时state[1]==thinking,state[2]==hungry,假设此时3号哲学家开始执行putdown,在putdown中通过enter(IM)进入管程,并修改自身状态state[3]=thinking,然后test((i-1)%5),即test(2),其中state[(2-1)%5]即state[1]!=eating成立,state[2]==hungry成立(表示之前2号哲学家已经执行了pickup,且没有成功取到叉子),state[(2+1)%5]即state[3]!=eating成立,即整个if条件成立,看到希望了,接着执行signal(self[2], self_count[2], IM),因为之前,2号哲学家已经在self[2]上等待许久了,对照signal操作的PV实现,self_count[2]>0,执行IM.next_count++,V(self[2]) (注:终于把2号哲学家等待许久的self[2]释放,唤醒2号哲学家),P(IM.next)阻塞自己。
分析:对于2号哲学家被唤醒之后,他将执行其pickup中P(self[2])之后的语句,self_count[2]--,即self_count[2]恢复为0,并最终执行pickup中leave(IM),在leave(IM)中参考Hoare管程leave操作的PV实现,判断if (IM.next_count)>0成立,则执行V(IM.next) 唤醒3号哲学家之前在P(IM.next)上的等待,并有机会执行后续语句IM.next_count--,即计数恢复为0,最终执行putdown中的leave(IM)退出管程,以便其他进程能够再进入管程,这里可以看出对于Hoare管程的实现,signal操作不必是过程体的最后一个操作。对于3号哲学家通过test(2)完成了唤醒2号哲学家的任务,然而他自己暂时阻塞在IM.next上,暂时不能执行putdown中后续的test((i+1)%5),即不能执行test(4)。
(F) 接着,假设在2号哲学家被唤醒取到叉子之后开始吃,3号哲学家执行完putdown中的leave(IM)退出管程,如果1号哲学家想再一次取叉子,此时state[0]==state[3]==thinking,state[2]==eating,1号不如之前顺畅,在pickup中通过enter(IM)进入管程,修改自身状态state[1]=hungry, 接着test(1)中,state[2]!=eating不成立,即if条件不通过,因此1号哲学家没有成功把自身状态修改为eating,也不用做test(1)中signal(self[1], self_count[1], IM)操作,再接着判断if (state[1]!=eating)成立,因此做wait(self[1], self_count[1], IM)操作,对照wait操作的PV实现,此时,self[1]_count++表示在self[1]等待的进程数加1,然后判断if (IM.next_count>0),如果此时2号进程还没有执行putdown中的signal操作,那么该条件不成立,然后执行V(IM.mutex)退出管程,接着P(self[1])阻塞自己,等待2号哲学家执行putdown中的signal操作唤醒之。
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
二、生产者消费者问题
多个生产者/多个消费者/多个缓冲单元
B : ARRAY[0..k-1] OF integer; sput: semaphore; /* 可以使用的空缓冲区数 */ sget: semaphore; /* 缓冲区内可以使用的产品数 */ sput := k; /* 缓冲区内允许放入k件产品 */ sget := 0; /* 缓冲区内没有产品 */ putptr, getptr : integer; putptr := 0; getptr := 0; s: semaphore; /* 互斥使用putptr, getptr */ s := 1; process Producer_i begin L1:produce a product; P(sput); P(s1); B[putptr] := product; putptr :=(putptr+1) mod k; V(s1); V(sget); goto L1; end; process Consumer_ j begin L2:P(sget); P(s2); Product:= B[getptr]; getptr:=(getptr+1) mod k; V(s2); V(sput); consume a product; goto L2; end;
需要注意的是无论在生产者进程中还是在消费者进程中,两个P操作的次序不能颠倒。
应先执行同步信号量的P操作,然后再执行互斥信号量的P操作,否则可能造成进程死锁。
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
三、读者写者问题
写者优先:
semaphore rmutex,wmutex,S; rmutex=1; wmutex=1; S=1; //增加互斥信号量S int readcount=0; //读进程计数 process reader_i( ) { while (true) { P(S); P(rmutex); if (readcount==0) P(wmutex); readcount++; V(rmutex); V(S); 读文件; P(rmutex); readcount--; if(readcount==0) V(wmutex); V(rmutex); } } process writer_i( ) { while(true) { P(S); P(wmutex); 写文件; V(wmutex); V(S); } } int readcount = 0, writecount = 0; semaphore x=1, y=1, z=1; // readcount,writecount互斥 semaphore rmutex=1,wmutex=1; // 读锁,写锁 process reader { P(z); P(rmutex); P(x); readcount++; if (readcount==1) P(wmutex); V(x); V(rmutex); V(z); read; P(x); readcount--; if (readcount==0) V(wmutex); V(x) }; process writer { P(y); writecount++; if (writecount==1) P(rmutex); V(y); P(wmutex); write; V(wmutex) P(y); writecount--; if (writecount==0) V(rmutex); V(y); };