问题描述
哲学家就餐问题可以这样表述,假设有五位哲学家围坐在一张圆形餐桌旁,做以下两件事情之一:吃饭,或者思考。吃东西的时候,他们就停止思考,思考的时候也停止吃东西。餐桌中间有一大碗意大利面,每两个哲学家之间有一只餐叉。因为用一只餐叉很难吃到意大利面,所以假设哲学家必须用两只餐叉吃东西。他们只能使用自己左右手边的那两只餐叉。哲学家从来不交谈,这就很危险,可能产生死锁,每个哲学家都拿着左手的餐叉,永远都在等右边的餐叉(或者相反)。即使没有死锁,也有可能发生资源耗尽,所以筷子是临界资源,同一根筷子只能被一位哲学家左手或右手拿起,即资源被占用,所以引出了一个资源分配的问题。
问题分析
如图所示,因为左右相邻的哲学家之间只有一根筷子(如图红色筷子),意味着相邻的哲学家只有一位能拿起中间这跟筷子,而不可能同时拿起这跟筷子,所以我们就遇到了资源临界问题。
资源临界
知识点引入
在进行问题解决前,我们需要有进程同步的知识储备:
P(S)原语:
①S减l;
②若S.value减1后仍大于或等于零,则进程继续执行;
③若S减l后小于零,则该进程被阻塞后并进入该信号相对应的等待队列中,然后转进程调度。
V(S)原语
①S加1;
②若相加结果大于零,进程继续执行;
③若相加结果小于或等于零,则从该信号的等待队列中唤醒一等待进程,然后再返回原进程继续执行或转进程调度。
即当资源要使用时运行P(S)原语,当资源使用完释放时运行V(S)原语。
代码分析
我们先通过伪代码进行分析:
筷子代码:
semaphore fork[5];//设置筷子量
for(int i = 0;i<5;i++)//使筷子初始化为1,1为筷子可用,0为已被占用
fork[i]=1;
//i= 0,1,2,3,4
哲学家代码:
process philosopher_i()//哲学家
{
while(true){
P(fork[i]); //左手拿起筷子
P(fork[(i+1)%5);//右手拿起筷子
eat();//模拟进餐
V(fork[i]);//左手释放筷子
V(fork[(i+1)%5];//右手释放筷子
}
}
解决哲学家进餐问题——方案一:至多允许四位哲学家同时去拿左边筷子
我们可以通过限制至多允许四位哲学家同时去拿左边筷子的方法来解决哲学家进餐问题,那么如果我们要实现,则要引入信息量设置:
semaphore control = 4;//控制至多4个人同时进餐
筷子代码:
semaphore fork[5];//设置筷子量
semaphore control = 4;//控制至多4个人同时进餐
for(int i = 0;i<5;i++)//使筷子初始化为1,1为筷子可用,0为已被占用
fork[i]=1;
哲学家代码:
process philosopher_i()//哲学家
{
while(true){
P(control);
P(fork[i]); //左手拿起筷子
V(control);
P(fork[(i+1)%5);//右手拿起筷子
eat();//模拟进餐
V(fork[i]);//左手释放筷子
V(fork[(i+1)%5];//右手释放筷子
}
}
误区
我在思考这个解决方案的时候查阅过网上相关代码,发现很多文章里存在一个误区,使得逻辑不严谨。
他们的V(control)原语都放在了进餐后,意为在control信号满的情况下,这个哲学家在左右手都拿到了筷子,且已用餐完了才会释放这个control信号,使得未双手都拿筷子的哲学家能拿起筷子进餐
process philosopher_i()//哲学家
{
while(true){
P(control);
P(fork[i]); //左手拿起筷子
P(fork[(i+1)%5);//右手拿起筷子
eat();//模拟进餐
V(fork[i]);//左手释放筷子
V(fork[(i+1)%5];//右手释放筷子
V(control);
}
}
而我认为,按照题目的逻辑,control信号只限制于至多4个哲学家左手拿筷子,使用P(control)与V(control)应该夹在P(fork[i])间。
P(control);
P(fork[i]); //左手拿起筷子
V(control);
解决哲学家进餐问题——方案二:规定奇数号哲学家先拿他左边的筷子,然后再去拿右边的筷子;偶数哲学家先拿他右边的筷子,然后再去拿左边的筷子
筷子代码:
semaphore fork[5];//设置筷子量
for(int i = 0;i<5;i++)//使筷子初始化为1,1为筷子可用,0为已被占用
fork[i]=1;
哲学家代码:
process philosopher_i()
{
while(true){
if(i%2==1)//如果为奇数号
{
P(fork[i]);
P(fork[(i+1)%5);
}
else//如果为偶数号
{
P(fork[(i+1)%5);
P(fork[i]); }
}
eat();
V(fork[i]);
V(fork[(i+1)%5];
}
}
解决哲学家进餐问题——方案三:使用AND型信号量,一位哲学家同时申请左右两根筷子
筷子代码:
semaphore fork[5];//设置筷子量
for(int i = 0;i<5;i++)//使筷子初始化为1,1为筷子可用,0为已被占用
fork[i]=1;
哲学家代码:
process philosopher_i()
{
while(true){
Swait(P(fork[i]),P(fork[(i+1)%5));//同时申请使用左右两根筷子
eat();
Ssignal(P(fork[i]),P(fork[(i+1)%5));//用餐完毕,释放左右两根筷子
}
}