哲学家进餐问题的算法与实现

哲学家进餐问题的算法与实现,第1张

1.哲学家进餐问题:

(1) 在什么情况下5 个哲学家全部吃不上饭考虑两种实现的方式,如下:

A. 算法描述: void philosopher(int i) /*i:哲学家编号,从0 到4*/ {while (TRUE) { think( )/*哲学家正在思考*/ take_fork(i)/*取左侧的筷子*/ take_fork((i+1) % N)/*取左侧筷子;%为取模运算*/eat( )/*吃饭*/put_fork(i)/*把左侧筷子放回桌子*/ put_fork((i+1) % N)/*把右侧筷子放回桌子*/ } } 分析:假如所有的哲学家都同时拿起左侧筷子,看到右侧筷子不可用,又都放下左侧筷子,等一会儿,又同时拿起左侧筷子,如此这般,永远重复。对于这种情况,即所有的程序都在无限期地运行,但是都无法取得任何进展,即出现饥饿,所有哲学家都吃不上饭。

B.算法描述:规定在拿到左侧的筷子后,先检查右面的筷子是否可用。如果不可用,则先放下左侧筷子,等一段时间再重复整个过程。分析:当出现以下情形,在某一个瞬间,所有的哲学家都同时启动这个算法,拿起左侧的筷子,而看到右侧筷子不可用,又都放下左侧筷子,等一会儿,又同时拿起左侧筷子……如此这样永远重复下去。对于这种情况,所有的程序都在运行,但却无法取得进展,即出现饥饿,所有的哲学家都吃不上饭。

(2) 描述一种没有人饿死(永远拿不到筷子)算法。考虑了四种实现的方式(A、B、C、D):

A.原理:至多只允许四个哲学家同时进餐,以保证至少有一个哲学家能够进餐,最终总会释放出他所使用过的两支筷子,从而可使更多的哲学家进餐。以下将room 作为信号量,只允许4 个哲学家同时进入餐厅就餐,这样就能保证至少有一个哲学家可以就餐,而申请进入餐厅的哲学家进入room 的等待队列,根据FIFO 的原则,总会进入到餐厅就餐,因此不会出现饿死和死锁的现象。

伪码: semaphore chopstick[5]={1,1,1,1,1}semaphore room=4void philosopher(int i) { while(true) {think()wait(room)//请求进入房间进餐 wait(chopstick[i])//请求左手边的筷子 wait(chopstick[(i+1)%5])//请求右手边的筷子 eat()signal(chopstick[(i+1)%5])//释放右手边的筷子 signal(chopstick[i])//释放左手边的筷子 signal(room)//退出房间释放信号量room } }

B.原理:仅当哲学家的左右两支筷子都可用时,才允许他拿起筷子进餐。

方法1:利用AND 型信号量机制实现:根据课程讲述,在一个原语中,将一段代码同时需要的多个临界资源,要么全部分配给它,要么一个都不分配,因此不会出现死锁的情形。当某些资源不够时阻塞调用进程由于等待队列的存在,使得对资源的请求满足FIFO 的要求,因此不会出现饥饿的情形。

伪码: semaphore chopstick[5]={1,1,1,1,1}void philosopher(int I) { while(true) { think()Swait(chopstick[(I+1)]%5,chopstick[I])eat()Ssignal(chopstick[(I+1)]%5,chopstick[I])} } 方法2:利用信号量的保护机制实现。通过信号量mutex对eat()之前的取左侧和右侧筷子的 *** 作进行保护,使之成为一个原子 *** 作,这样可以防止死锁的出现。伪码: semaphore mutex = 1 semaphorechopstick[5]={1,1,1,1,1}void philosopher(int I) { while(true) { think()wait(mutex)wait(chopstick[(I+1)]%5)wait(chopstick[I])signal(mutex)eat()signal(chopstick[(I+1)]%5)signal(chopstick[I])} }

C.原理:规定奇数号的哲学家先拿起他左边的筷子,然后再去拿他右边的筷子而偶数号的哲学家则相反.按此规定,将是1,2号哲学家竞争1号筷子,3,4号哲学家竞争3号筷子.即五个哲学家都竞争奇数号筷子,获得后,再去竞争偶数号筷子,最后总会有一个哲学家能获得两支筷子而进餐。而申请不到的哲学家进入阻塞等待队列,根FIFO原则,则先申请的哲学家会较先可以吃饭,因此不会出现饿死的哲学家。

伪码: semaphore chopstick[5]={1,1,1,1,1}void philosopher(int i) { while(true) { think()if(i%2== 0) //偶数哲学家,先右后左。 { wait (chopstick[ i + 1 ] mod 5) wait (chopstick[ i]) eat()signal (chopstick[ i + 1 ] mod 5) signal (chopstick[ i]) } Else //奇数哲学家,先左后右。 { wait (chopstick[ i]) wait (chopstick[ i +1 ] mod 5) eat()signal (chopstick[ i]) signal (chopstick[ i + 1 ] mod 5)} }

D.利用管程机制实现(最终该实现是失败的,见以下分析):原理:不是对每只筷子设置信号量,而是对每个哲学家设置信号量。test()函数有以下作用:

a. 如果当前处理的哲学家处于饥饿状态且两侧哲学家不在吃饭状态,则当前哲学家通过 test()函数试图进入吃饭状态。

b. 如果通过test()进入吃饭状态不成功,那么当前哲学家就在该信号量阻塞等待,直到其他的哲学家进程通过test()将该哲学家的状态设置为EATING。

c. 当一个哲学家进程调用put_forks()放下筷子的时候,会通过test()测试它的邻居,如果邻居处于饥饿状态,且该邻居的邻居不在吃饭状态,则该邻居进入吃饭状态。由上所述,该算法不会出现死锁,因为一个哲学家只有在两个邻座都不在进餐时,才允许转换到进餐状态。该算法会出现某个哲学家适终无法吃饭的情况,即当该哲学家的左右两个哲学家交替处在吃饭的状态的时候,则该哲学家始终无法进入吃饭的状态,因此不满足题目的要求。但是该算法能够实现对于任意多位哲学家的情况都能获得最大的并行度,因此具有重要的意义。

伪码:#define N 5 /* 哲学家人数*/#define LEFT (i-1+N)%N /* i的左邻号码 */ #define RIGHT (i+1)%N /* i的右邻号码 */ typedef enum { THINKING, HUNGRY, EATING } phil_state/*哲学家状态*/ monitor dp /*管程*/ { phil_state state[N]semaphore mutex =1semaphore s[N]/*每个哲学家一个信号量,初始值为0*/void test(int i) { if ( state[i] == HUNGRY &&state[LEFT(i)] != EATING&&state[RIGHT(i)] != EATING ) { state[i] = EATINGV(s[i])} } void get_forks(inti) { P(mutex)state[i] = HUNGRYtest(i)/*试图得到两支筷子*/ V(mutex)P(s[i])/*得不到筷子则阻塞*/ } void put_forks(int i) { P(mutex)state[i]= THINKINGtest(LEFT(i))/*看左邻是否进餐*/ test(RIGHT(i))/*看右邻是否进餐*/ V(mutex)} } 哲学家进程如下: void philosopher(int process) { while(true) { think()get_forks(process)eat()put_forks(process)} }

2.理发师问题:一个理发店有一个入口和一个出口。理发店内有一个可站5 位顾客的站席区、4 个单人沙发、3 个理发师及其专用理发工具、一个收银台。新来的顾客坐在沙发上等待;没有空沙发时,可在站席区等待;站席区满时,只能在入口外等待。理发师可从事理发、收银和休息三种活动。

理发店的活动满足下列条件:

1)休息的理发师是坐地自己专用的理发椅上,不会占用顾客的沙发;

2)处理休息状态的理发师可为在沙发上等待时间最长的顾客理发;

3)理发时间长短由理发师决定;

4)在站席区等待时间最长的顾客可坐到空闲的理发上;

5)任何时刻最多只能有一个理发师在收银。试用信号量机制或管程机制实现理发师进程和顾客进程。

原理:

(1) customer 进程:首先检查站席区是否已满(stand_capacity),若满选择离开,否则进入站席区,即进入理发店。在站席区等待沙发的空位(信号量sofa),如果沙发已满,则进入阻塞等待队列,直到出现空位,在站席区中等待时间最长的顾客离开站席区(stand_capacity)。坐到沙发上,等待理发椅(barber_chair),如果理发椅已满,则进入阻塞等待队列,直到出现空位,在沙发上等待时间最长的顾客离开沙发(释放信号量sofa)。坐到理发椅上,释放准备好的信号(customer_ready),获得该理发师的编号(0~1 的数字)。等待理发师理发结束(finished[barber_number])。在离开理发椅之前付款(payment),等待收据 (receipt),离开理发椅(leave_barberchair)。最后离开理发店。

这里需要注意几点:

a) 首先是几个需要进行互斥处理的地方,主要包括:进入站席区、进入沙发、进入理发椅和付款几个地方。

b) 通过barber_chair保证一个理发椅上最多只有一名顾客。但这也不够,因为单凭 baber_chair 无法保证一名顾客离开理发椅之前,另一位顾客不会坐到该理发椅上,因此增加信号量leave_barberchair,让顾客离开理发椅后,释放该信号,而理发师接收到该信号后才释放barber_chair 等待下一位顾客。

c) 在理发的过程中,需要保证是自己理发完毕,才能够进行下面的付款、离开理发椅的活动。这个机制是通过customer 进程获得给他理发的理发师编号来实现的,这样,当该编号的理发师释放对应的finished[i]信号的时候,该顾客才理发完毕。

d) 理发师是通过mutex 信号量保证他们每个人同时只进行一项 *** 作(理发或者收款)。

e) 为了保证该顾客理发完毕后马上可以付款离开,就应该保证给该顾客理发的理发师在理发完毕后马上到收银台进入收款 *** 作而不是给下一位顾客服务。在伪码中由以下机制实现:即顾客在释放离开理发椅的信号前,发出付款的信号。这样该理发师得不到顾客的离开理发椅的信号,不能进入下一个循环为下一名顾客服务,而只能进入收款台的收款 *** 作。直到顾客接到收据后,才释放离开理发椅的信号,离开理发椅,让理发师释放该理发椅的信号,让下一位等待的顾客坐到理发椅上。

(2)barber 进程首先将该理发师的编号压入队列,供顾客提取。等待顾客坐到理发椅坐好(信号量 customer_ready),开始理发,理发结束后释放结束信号(finished[i])。等待顾客离开理发椅(leave_barberchair)(期间去收银台进行收款活动),释放理发椅空闲信号(barber_chair),等待下一位顾客坐上来。

(3)cash(收银台)进程等待顾客付款(payment),执行收款 *** 作,收款 *** 作结束,给付收据(receipt)。信号量总表:信号量 waitsignal stand_capacity 顾客等待进入理发店顾客离开站席区 sofa 顾客等待坐到沙发顾客离开沙发 barber_chair 顾客等待空理发椅理发师释放空理发椅 customer_ready 理发师等待,直到一个顾客坐到理发椅顾客坐到理发椅上,给理发师发出信号 mutex 等待理发师空闲,执行理发或收款 *** 作理发师执行理发或收款结束,进入空闲状态 mutex1执行入队或出队等待入队或出队结束,释放信号 finished[i] 顾客等待对应编号理发师理发结束理发师理发结束,释放信号 leave_barberchair 理发师等待顾客离开理发椅顾客付款完毕得到收据,离开理发椅释放信号 payment 收银员等待顾客付款顾客付款,发出信号 receipt 顾客等待收银员收、开具收据收银员收款结束、开具收据,释放信号

伪码: semaphore stand_capacity=5semaphore sofa=4semaphorebarber_chair=3semaphore customer_ready=0semaphore mutex=3semaphoremutex1=1semaphore finished[3]={0,0,0}semaphore leave_barberchair=0semaphore payment=0semaphore receipt=0void customer() { int barber_numberwait(stand_capacity)//等待进入理发店 enter_room()//进入理发店 wait(sofa)//等待沙发 leave_stand_section()//离开站席区 signal(stand_capacity)sit_on_sofa()//坐在沙发上 wait(barber_chair)//等待理发椅 get_up_sofa()//离开沙发 signal(sofa)wait(mutex1)sit_on_barberchair()//坐到理发椅上 signal(customer_ready)barber_number=dequeue()//得到理发师编号 signal(mutex1)wait(finished[barber_number])//等待理发结束 pay()//付款 signal(payment)//付款 wait(receipt)//等待收据 get_up_barberchair()//离开理发椅 signal(leave_barberchair)//发出离开理发椅信号 exit_shop()//了离开理发店 } void barber(int i) { while(true) { wait(mutex1)enqueue(i)//将该理发师的编号加入队列 signal(mutex1)wait(customer_ready)//等待顾客准备好 wait(mutex)cut_hair()//理发 signal(mutex)signal(finished[i])//理发结束 wait(leave_barberchair)//等待顾客离开理发椅信号 signal(barber_chair)//释放barber_chair 信号 } } void cash() //收银 { while(true) { wait(payment)//等待顾客付款 wait(mutex)//原子 *** 作 get_pay()//接受付款 give_receipt()//给顾客收据 signal(mutex)signal(receipt)//收银完毕,释放信号 } }

分析:在分析该问题过程中,出现若干问题,是参阅相关资料后才认识到这些问题的隐蔽性和严重性的,主要包括:

(1)在顾客进程,如果是在释放leave_barberchair 信号之后进行付款动作的话,很容易造成没有收银员为其收款的情形,原因是:为该顾客理发的理发师收到 leave_barberchair 信号后,释放barber_chair 信号,另外一名顾客坐到理发椅上,该理发师有可能为这另外一名顾客理发,而没有为刚理完发的顾客收款。为解决这个问题,就是采取在释放leave_barberchair 信号之前,完成付款 *** 作。这样该理发师无法进入下一轮循环为另外顾客服务,只能到收银台收款。

(2)本算法是通过给理发师编号的方式,当顾客坐到某理发椅上也同时获得理发师的编号,如此,当该理发师理发结束,释放信号,顾客只有接收到为其理发的理发师的理发结束信号才会进行付款等 *** 作。这样实现,是为避免这样的错误,即:如果仅用一个finished 信号量的话,很容易出现别的理发师理发完毕释放了finished 信号,把正在理发的这位顾客赶去付款,而已经理完发的顾客却被阻塞在理发椅上的情形。当然也可以为顾客进行编号,让理发师获取他理发的顾客的编号,但这样就会限制顾客的数量,因为finished[] 数组不能是无限的。而为理发师编号,则只需要三个元素即可。

虽然不是C#,但意思如下,希望能帮到你。。。。。这是我自己以前做过的。

#include <stdio.h>

struct{

char pstatus //哲学家状态

int pid//哲学家编号

int waiter1

int priority//优先级,数值大的就餐的优先级高

}pcb[6]

struct sp{

int stick //筷子状态,0表示可使用或表示空闲,1表示有人在使用或不空闲

}num[6]//筷子编号

struct{

int value  //互斥信号量,临界区数,初值为1

int waiter2 //同步信号量,初值为0

}sem[5]

char stack[11][6]

int m1,m2//为系统设置的公用数据被三个进程共享使用。

int i,ep  //ep表示正在就餐的哲学家的pid,i为通用寄存器

char addr  //addr为程序计数器

void init()//初始化函数

int find()//查找

int hungry()

int process1()

int process2()

int process3()

int process4()

int process5()

int p(int,int ,char)

int v(int,int ,char)

int main(){

init()

printf("系统程序开始执行\n")

for()

{   if( find()!=0 )  hungry()

else  break

}

printf("系统程序结束\n")

}

void init(){

int j,k

pcb[0].pstatus='w'

pcb[0].priority=2//数越大优先级越高

for( j=1j<=5j++)

{

num[j].stick=0

pcb[j].pid=j

pcb[j].pstatus='t'

pcb[j].priority=5

}

for(j=1j<=5j++)

{

sem[j].value=1

sem[j].waiter2=0

}

i=0ep=0m1=0 m2=0

for(j=1j<=11j++)

{

for(k=1k<=6k++)

stack[j][k]='0'

}

}

int find()

{

int j

for(j=1j<=5j++)

if(pcb[j].pstatus=='t'&&pcb[j].priority==5)return (j)

return 0

}

int hungry(){

int j,left,right 

j=find()

left=j

right=j+1

if(j==0) return(0)

/*else if(num[left].stick==1||num[right].stick==1){

num[left].stick=0

num[right].stick=0

pcb[j].pstatus='w'

printf("在等吃饭的哲学家编号为%d\n",pcb[j].pid)}*/

if(ep==0){

//&&num[left].stick==0 &&num[right].stick==0&&pcb[j].pstatus=='t'&&pcb[j].priority==5

if(num[left].stick==0&&num[right].stick==0)

{

printf("将正在思考的哲学家%d状态设为执行状态\n",pcb[j].pid)

printf("读取进程%d\n",pcb[j].pid)

pcb[j].pstatus='e'

ep=j

num[left].stick=1

num[right].stick=1

printf("运行进程%d\n",ep)

pcb[ep].priority--

}

else{

printf("哲学家%d没有同时获得左右两只筷子,只好放下筷子等待吃饭\n",pcb[j].pid)

num[left].stick=0

num[right].stick=0

pcb[j].pstatus='w'

pcb[j].priority++

if(pcb[j].pstatus=='w'&&pcb[j].priority>pcb[j-1].priority)

{ printf("将在等待中哲学家%d设为t状态\n",pcb[j].pid)

pcb[j].pstatus='t' 

printf("读取进程%d\n",pcb[j].pid)

pcb[j].pstatus='e'

ep=j}

}

}

i=stack[1][ep]

addr=stack[2][ep]

switch(ep){

case 1:process1()

break

case 2:process2()

break

case 3:process3()

break

case 4:process4()

break

case 5:process5()

break

default:printf("当前进程出错!%d\n",ep)

break

}

}

process1(){

if(addr=='m') goto m

i=1

a:

printf("哲学家1在信号量sem[1]上调用P *** 作\n")

if(p(1,1,'m')==0) return(0)

else goto m

m:

printf("打印进程1...m1=%d\n",m1)

printf("打印进程1...i=%d\n",i)

i+=5

goto a

}

process2(){

if(addr=='m') goto m

if(addr=='n') goto n

i=1

a:

printf("进程2在信号量sem[3]上调用P *** 作\n")

if(p(3,2,'m')==0) return(0)

m:

m1=m2+4

printf("进程2在信号量sem[2]上调用V *** 作m1=%d\n",m1)

if(v(2,2,'n')==0) return(0)

else{

n:

printf("打印进程2...i=%d\n",i)

i+=10

goto a

}

}

process3(){

if(addr=='m') goto m

if(addr=='n') goto n

i=1

a:

printf("进程3在信号量sem[4]上调用P *** 作\n")

if(p(4,3,'m')==0) return(0)

m:

m1=m2+4

printf("进程3在信号量sem[2]上调用V *** 作m1=%d\n",m1)

if(v(2,3,'n')==0) return(0)

else{

n:

printf("打印进程3...i=%d\n",i)

i+=15

goto a

}

}

process4(){

if(addr=='m') goto m

if(addr=='n') goto n

i=1

a:

printf("进程4在信号量sem[4]上调用P *** 作\n")

if(p(4,4,'m')==0) return(0)

m:

m1=m2+4

printf("进程4在信号量sem[3]上调用V *** 作m1=%d\n",m1)

if(v(3,4,'n')==0) return(0)

else{

n:

printf("打印进程4...i=%d\n",i)

i+=20

goto a

}

}

process5(){

if(addr=='m') goto m

if(addr=='n') goto n

i=1

a:

if(i>1){

printf("进程5在信号量sem[2]上调用P *** 作\n")

if(p(2,5,'n')==0) return(0)

}

n:

m2=i

printf("进程5在sem[5]信号量上调用V *** 作m=%d\n",m2)

if(v(5,5,'m')==0) return(0)

else{

m:

i+=1

goto a

}

}

int p(int se,int p,char ad){ //p *** 作

int w  //等待指示字单元

sem[se].value--

if(sem[se].value==0) return(1) //判断是否有资源

printf("阻塞当前进程%d\n",p) //打印阻塞进程

pcb[p].pstatus='w' //把阻塞进程置"w"状态

ep=0

pcb[p].waiter1=0

w=sem[se].waiter2

//下一状态指示字赋给w

if(w==0) sem[se].waiter2=p //把等待进程标识符送入信号单元

else{

while(pcb[w].waiter1!=0) //找等待队列是否为空

w=pcb[w].waiter1 //送入等待指示字单元

pcb[w].waiter1=p //否则插入等待队列

}

stack[1][p]=i

stack[2][p]=ad// 保护现场

return(0)

}

int v(int se,int p,char ad){

int w

sem[se].value++

if(sem[se].value>0) return(1)

w=sem[se].waiter2

sem[se].waiter2=pcb[w].waiter1

pcb[w].pstatus='t'

printf("唤醒进程%d\n",w)

stack[1][p]=i

stack[2][p]=ad

return(0)

}


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/yw/8723839.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-04-20
下一篇 2023-04-20

发表评论

登录后才能评论

评论列表(0条)

保存