1、哲学家就餐问题描述
\qquad
哲学家就餐问题是由Dijkstra在1965年提出的,5个哲学家围绕一张圆桌坐,桌子上放有5支叉子,每两个哲学家之间放一支;哲学家的动作包括思考和进餐,进餐时需要同时拿起他左边的叉子和右边的叉子,思考时则同时将两支叉子放回原处。问如何保证哲学家们的动作有序进行?如不出现有人永远不能拿到叉子。哲学家就餐问题的示意图如下图所示。
\qquad
设计原则:某个哲学家要么不拿叉子,要么拿两把叉子。
S1:思考中...
S2:进入饥饿状态
S3:如果左邻居或者右邻居正在进餐,等待;否则转到S4;
S4:拿起两把叉子;
S5:吃面条...
S6:放下左边的叉子
S7:放下右边的叉子
S8:开始新的循环,转到S1
2、思路设计
\qquad 首先必须要有一个数据结构,来描述每个哲学家的当前状态;
#define N 5 //哲学家的个数
#definde LEFT i //第i个哲学家的左邻居
#define RIGHT (i+1)%N //第i个哲学家的右邻居
#define THINKING 0 //思考状态
#define HUNGRY 1 //饥饿状态
#define EATING 2 //进餐状态
int state[N]; //记录每个人的状态
\qquad 该状态是一个临界资源,对它的访问应该互斥地进行
semaphore mutex; //互斥信号量,初值为1
\qquad 一个哲学家吃饱之后,可能要唤醒邻居,存在同步关系
semaphore s[N]; //同步信号量,初值为0
3、实现
\qquad 函数philosopher的定义如下所示:
void philosopher(int i)
{
while(true)
{
think(); //思考中,S1
take_forks(i); //拿到两把叉子或者被阻塞,S2-S4
eat(); //吃面条中...S5
put_forks(i); //把两把叉子放回到原处S6-S7
}
}
\qquad 函数take_forks的定义
void take_forks(int i) //i的取值为:0 ~ N-1
{
P(mutex); //进入临界区
state[i] = HUNGRY; //将状态置为饥饿
test_take_left_right_forks(i); //试图拿两把叉子
V(mutex); //退出临界区
P(s[i]); //没有叉子便阻塞
}
\qquad 函数test_take_left_right_forks的定义
void test_take_left_right_forks(int i)
{
if(state[i] == HUNGRY) && //i是哲学家自己或者其他哲学家
state[LEFT] != EATING &&
state[RIGHT] != EATING)
{
state[i] = EATING;
V(S[i]); //通知第i个人可以吃饭了
}
}
\qquad 函数put_forks的定义
//功能:把两把叉子放回到原处,并在需要的时候取唤醒左邻右舍
void put_forks(int i)
{
P(mutex); //进入临界区
state[i] = THINKING; //交出两把叉子
test_take_left_right_forks(LEFT); //判断左邻居是否能够吃饭
test_take_left_right_forks(RIGHT); //判断右邻居是否能够吃法
V(mutex); //退出临界区
}
\qquad 其他两个函数think()和eat(),eat()函数不需要加互斥锁,think()函数需要加互斥锁。
void think()
{
P(mutex);
i_am_thinking(); //思考的动作
V(mutex);
}