一、哲学家进餐问题
由Dijkstra提出并解决的哲学家进餐问题(The Dinning Philosophers Problem)是典型的同步问题。
该问题是描述有五个哲学家共用一张圆桌,分别坐在周围的五张椅子上,在圆桌上有五个碗和五只筷子,他们的生活方式是交替地进行思考和进餐。平时,一个哲学家进行思考,饥饿时便试图取用其左右最靠近他的筷子,只有在他拿到两只筷子时才能进餐。进餐毕,放下筷子继续思考。
1.利用记录型信号量解决哲学家解决问题
经分析可知,放在桌子上的筷子是临界资源,在一段时间内只允许一位哲学家使用。为了实现对筷子的互斥使用,可以用一个信号量表示一只筷子,由这五个信号量构成信号量数组。其描述如下:
semaphore chopstick[5]={1,1,1,1,1};
所有的信号量均被初始化为1,
第i位哲学家的活动可描述为:
do{
wait(chopstick[i]);
wait(chopstick[(i+1)%5]);
…
//eat
signal(chopstick[i]);
signal(chopstick[(i+1)%5]);
…
//think
…
}while(TRUE);
在以上描述中,当哲学家饥饿时,总是先去拿他左边的筷子,即执行wait(chopstick[i]);成功后,再去拿他右边的筷子,即执行wait(chopstick[(i+1)%5]);又成功后便可进餐。进餐毕,又先放下他左边的筷子,然后再放他右边的筷子。虽然,上述解法可保证不会有两个相邻的哲学家同时进餐,但却有可能引起死锁。假如五位哲学家同时饥饿而各自拿起左边的筷子时,就会使五个信号量chopstick均为0;当他们再试图去拿右边的筷子时,都将因无筷子可拿而无限期地等待。
对于这样的死锁问题,可采取以下几种解决方法:
(1)至多只允许有四位哲学家同时去拿左边的筷子,最终能保证至少有一位哲学家能够进餐,并在用毕时能释放出他用过的两只筷子,从而使更多的哲学家能够进餐。
(2)仅当哲学家的左、右两只筷子均可用时,才允许他拿起筷子进餐。
(3)规定奇数号哲学家先拿他左边的筷子,然后再去拿右边的筷子;而偶数号哲学家则相反。按此规定,将是1、2号哲学家竞争1号筷子;3、4号筷子竞争3号筷子。即五位哲学家都先竞争奇数号筷子,获得后,再去竞争偶数号筷子,最后总会有一位哲学家能获得两只筷子而进餐。
2.利用AND信号量机制解决哲学家进餐问题
在哲学家进餐问题中,要求每个哲学家先获得两个临界资源(筷子)后方能进餐,这在本质上就是前面所介绍的AND同步问题。故用AND信号量机制可获得最简洁的解法。
semaphore chopstick[5]={1,1,1,1,1};
do{
…
//think
…
Swait(chopstick[(i+1)%5],chopstick[i]);
…
//eat
…
Signal(chopstick[(i+1)%5],chopstick[i]);
}while(TRUE);
二、读者-写者问题
读者-写者(Reader-Writer Problem)问题是指保证一个Writer进程必须与其他进程互斥地访问共享对象的同步问题。读者-写者问题常被用来测试新同步原语。
1.利用记录型信号量解决读者-写者问题
为实现Reader与Writer进程间在读或写时的互斥而设置了一个互斥信号量wmutex。另外,再设置一个整型变量readcount表示正在读的进程数目。由于只要有一个Reader进程在读,便不允许Writer进程去写。因此,仅当readcount=0,表示尚无Reader进程在读时,Reader进程才需要执行wait(wmutex)操作。若wait(wmutex)操作成功,Reader进程便可去读,相应地,做readcount+1操作。同理,仅当Reader进程在执行了readcount减1操作后其值为0时,才须执行signal(wmutex)操作,以便让Writer进程写操作。又因为readcount是一个可被多个Reader进程访问的临界资源,因此,也应该为它设置一个互斥信号量rmutex。
读者-写者问题可描述如下:
semaphore rmutex=1,wmutex=1;
int readcount=0;
void reader(){
do{
wait(rmutex);
if(readcount==0)
wait(wmutex);
readcount++;
signal(rmutex);
…
perform read operation;
…
wait(rmutex);
readcount--;
if(readcount==0)
signal(wmutex);
signal(rmutex);
}while(TRUE);
}
void Writer(){
do{
wait(wmutex);
perform write operation;
signal(wmutex);
}while(TRUE);
}
void main(){
cobegin
Reader();
Writer();
coend
}
2.利用信号量集机制解决读者-写者问题
这里的读者-写者问题,与前面的略有不同,它增加了一个限制,即最多只允许RN个读者同时读。为此,又引入了一个信号量L,并赋予其值为RN,通过执行wait(L,1,1)操作来控制读者的数目,每当有一个读者进入时,就要先执行wait(L,1,1)操作,使L的值减1。当有RN个读者进入读后,L便减为0,第RN+1个读者要进入读时,必然会因wait(L,1,1)操作失败而阻塞。
对利用信号量集来解决读者-写者问题的描述如下:
int RN;
semphore L=RN,mx=1;
void Reader(){
do{
Swait(L,1,1);
Swait(mx,1,0);
…
perform read operation;
…
Ssignal(L,1);
}while(TRUE);
}
void Writer(){
do{
Swait(mx,1,1;L,RN,0);
perform write operation;
Ssignal(mx,1);
}while(TRUE);
}
void main(){
cobegin
Reader();
Writer();
coend
}
其中,Swait(mx,1,0)语句起着开关的作用。只要无writer进程进入写操作,mx=1,reader进程就都可以进入读操作。但只要一旦有writer进程写操作时,其mx=0,则任何reader进程就都可以进入读操作。Swait(mx,1,1,L,RN,0)语句表示仅当既无writer进程在写操作(mx=1)、又无reader进程在读操作(L=RN)时,writer进程才能进入临界区进行写操作。
三、举例说明
1.桌子上有一空盘,允许放一只水果。爸爸可向盘中放苹果也可向盘中放桔子,儿子专等吃盘中的桔子,女人专等吃盘中的苹果。规定当盘空时一次只能放一只水果供吃者取用,请用P、V原语实现爸爸、儿子、女儿三个并发进程的同步。
分析:同步和互斥问题
1.考虑同步情况即所有的“等待情况”:
①爸爸要等待盘子为空;
②儿子要等待盘中水果为桔子;
③女儿要等待盘中水果为苹果。
2.互斥
看起来盘子好像是要作互斥处理的,但由于题目中的爸爸、儿子、女儿均只有一个,并且他们访问盘子的条件都不一样,所有他们根本不会同时去访问盘子,因此盘子也就不用作互斥处理了。
并发进程同步描述如下:
设置3个信号量,信号量Orange表示盘中有桔子,初值为0;信号量Apple表示盘中有苹果,初值为0;信号量EmptyDish表示盘子为空,初值为1。
int Orange=0,Apple=0,EmptyDish=1;
main(){
cobegin
father();
son();
daughter();
coend
}
father(){
while(1){
P(EmptyDish);
将水果放入盘中;
if(放入的水果是桔子)
V(Orange);
else
V(Apple);
}
}
son(){
while(1){
P(Orange);
从盘中取出桔子;
V(EmptyDish);
吃桔子;
}
}
daughter(){
while(1){
P(Apple);
从盘中取出苹果;
V(EmptyDish);
吃苹果;
}
}
2.设公共汽车上,司机和售票员的活动分别是:
在汽车不断地到站、停车、行驶过程中,这两个活动有什么同步关系?用信号量和P、V操作实现他们的同步。
分析:
①确定进程间的同步关系。在汽车行驶过程中,售票员关车门后,向司机发出开车信号,司机接到开车信号后启动车辆;在汽车正常行驶过程中售票员售票,到站时司机停车,售票员在车停后开车门让乘客下车。
②确定信号量及其值。由于司机和售票员之间要互通消息,司机进程设置一个私有信号量run,用于判断司机能否进行工作,初值为0;售票员进程设置一个私有信号量stop,用于判断是否停车,售票员是否能够打开车门,初值为0。
同步描述如下:
int run=0,stop=0;
main(){
parbegin
driver();
busman();
parend
}
driver(){
while(1){
P(run);
启动车辆;
正常行车;
到站停车;
V(stop);
}
}
busman(){
while(1){
关车门;
V(run);
售票;
P(stop);
开车门;
下乘客;
}
}