2.23 哲学家就餐问题
1965年由Dijkstra提出
问题描述
- 5个哲学家围坐圆桌
- 5只餐叉间隔摆放
- 哲学家行为为思考或进餐
- 哲学家进餐必须同时使用两边的餐叉
- 思考时蒋餐叉放回原处
- 两个哲学家不使用同一把叉子
- 尽量避免死锁和饥饿
如图
方案一 取左叉
都去拿左边的叉子,死锁
semaphore fork[5] = {1, 1, 1, 1, 1};
void main()
{
cobegin {philosopher(0);
philosopher(1);
philosopher(2);
philosopher(3);
philosopher(4);
}coend;
}
void philosopher(int i)
{
while(true) {
think; //思考
wait(fork[i]); //拿起左边的叉子
wait(fork[(i+1)%5]); //拿起右边的叉子
eat();
signal(fork[i]); //放回左边的叉子
signal(fork[(i+1)%5]); //放回右边的叉子
}
}
方案二
请求同时拿两个叉子,被占则等待随机事件再请求,可能活锁
semaphore fork[5] = {1, 1, 1, 1, 1};
void main()
{
cobegin {philosopher(0);
philosopher(1);
philosopher(2);
philosopher(3);
philosopher(4);
}coend;
}
void philosopher(int i)
{
while(true) {
think; //思考
wait(fork[i]); //拿起左边的叉子
timeout(wait(fork[(i+1)%5], [0, T]) //若右边的叉子被占用,则放下左边叉,等待一段随机时间后再拿
eat();
signal(fork[i]); //放回左边的叉子
signal(fork[(i+1)%5]); //放回右边的叉子
}
}
方案三 资源分级
为资源(这里是餐叉)分配一个偏序(partial order)或者分级(hierarchy)的关系,并约定所有资源都按照这种顺序获取,按相反顺序释放,而且保证不会有两个无关资源同时被同一项工作所需要
① 为餐叉编号
- 就餐前,先取用编号较低的餐叉,再取用编号较高的餐叉
- 就餐毕,先放下编号较高的餐叉,再放下编号较低的餐叉
semaphore fork[5] = {1, 1, 1, 1, 1};
void main()
{
cobegin {philosopher(0);
philosopher(1);
philosopher(2);
philosopher(3);
philosopher(4);
}coend;
}
void philosopher(int i)
{
while(true) {
think(); //思考
if (i != 4) {
wait(fork[i]); wait(fork[(i+1)%5]);} //先左后右
else {
wait(fork[(i+1)%5]); wait(fork[i]);} //先右后左
eat();
if (i != 4) {
signal(fork[(i+1)%5]); signal(fork[i]);} //先右后左
else {
signal(fork[i]); signal(fork[(i+1)%5]);} //先左后右
}
}
② 为哲学家编号
- 奇数号的哲学家必须首先拿左边的餐叉
- 偶数号的哲学家必须首先拿右边的餐叉
semaphore fork[5] = {1, 1, 1, 1, 1};
void main()
{
cobegin {philosopher(0);
philosopher(1);
philosopher(2);
philosopher(3);
philosopher(4);
}coend;
}
void philosopher(int i)
{
while(true) {
think(); //思考
if (i % 2 != 0) {
wait(fork[i]); wait(fork[(i+1)%5]);} //先左后右
else {
wait(fork[(i+1)%5]); wait(fork[i]);}
eat();
signal(fork[(i+1)%5]); //先右后左
signal(fork[i]);
}
}
方案四 服务生方法
- 引入一个餐厅服务生,哲学家必须经过他的允许才能拿起餐叉
- 最多允许4个哲学家同时进食
semaphore fork[5] = {1, 1, 1, 1, 1}, room = 4;
void main()
{
cobegin {philosopher(0);
philosopher(1);
philosopher(2);
philosopher(3);
philosopher(4);
}coend;
}
void philosopher(int i)
{
while(true) {
think; //思考
wait(room); //占据就餐位置
wait(fork[i]); //拿起左边的叉子
wait(fork[(i+1)%5]); //拿起右边的叉子
signal(fork[i]); //放回左边的叉子
signal(fork[(i+1)%5]); //放回右边的叉子
signal(room); //释放就餐位置
}
}
引申:And型信号量集
- 在一个原语中申请需要的多个临界资源,要么全部分,要么一个都不分配
- AND型信号量集P原语为Swait(Simultaneous Wait),V原语为Ssignal(Simultaneous Signal)。
- Swait(S1, S2, …, Sn)
- Ssignal(S1, S2, …, Sn)
管程方案
monitor dining controller;
cond ForkReady [5]; /*condition variable for synchronization */
boolean fork[ 5]={ true } ; /*availability status of each fork*/
void get_forks (int pid) /*pid is the philosopher id number */
{
int left = pid;
int right =(++pid)%5;
/*grant the left fork*/
if( ! fork [ left])
cwait(ForkReady [ left] ); /*queue on condition variable */
fork [ left]= false ;
/*grant the right fork*/
if( ! fork [ right] )
cwait(ForkReady [right] );
/*queue on condition variable */
fork [ right] = false;
}
void release_forks ( int pid){
int left = pid;
int right =(++pid)%5;
/*release the left fork*/
if( empty (ForkReady [ leftl) /*no one is waiting for this fork */
fork [ left] = true;
else /*awaken a process waiting on this fork */
csignal (ForkReady [ left]);
/*release the right fork*/
if (empty (ForkReady [right]) /*no one is waiting for this fork*/
fork [ right]= true;
else /*awaken a process waiting on this fork*/
csignal (ForkReady [right] );
}
void philosopher [ k=0 to 4] /*the five philosopher clients */
{
while (true){
<think>;
get_forks (k); /*client requests two forks via monitor * /
<eat spaghetti>;
release_forks (k); /*client releases forks via the monitor */
}
}