就餐问题

哲学家就餐问题可以这样表述,假设有五位哲学家围坐在一张圆形餐桌旁,做以下两件事情之一:吃饭,或者思考。吃东西的时候,他们就停止思考,思考的时候也停止吃东西。餐桌中间有一大碗意大利面,每两个哲学家之间有一只餐叉。因为用一只餐叉很难吃到意大利面,所以假设哲学家必须用两只餐叉吃东西。他们只能使用自己左右手边的那两只餐叉。哲学家就餐问题有时也用米饭和筷子而不是意大利面和餐叉来描述,因为很明显,吃米饭必须用两根筷子。

哲学家就餐问题的演示

哲学家从来不交谈,这就很危险,可能产生死锁,每个哲学家都拿着左手的餐叉,永远都在等右边的餐叉(或者相反)。

即使没有死锁,也有可能发生资源耗尽。例如,假设规定当哲学家等待另一只餐叉超过五分钟后就放下自己手里的那一只餐叉,并且再等五分钟后进行下一次尝试。这个策略消除了死锁(系统总会进入到下一个状态),但仍然有可能发生“活锁”。如果五位哲学家在完全相同的时刻进入餐厅,并同时拿起左边的餐叉,那么这些哲学家就会等待五分钟,同时放下手中的餐叉,再等五分钟,又同时拿起这些餐叉。

在实际的计算机问题中,缺乏餐叉可以类比为缺乏共享资源。一种常用的计算机技术是资源加锁,用来保证在某个时刻,资源只能被一个程序或一段代码访问。当一个程序想要使用的资源已经被另一个程序锁定,它就等待资源解锁。当多个程序涉及到加锁的资源时,在某些情况下就有可能发生死锁。例如,某个程序需要访问两个文件,当两个这样的程序各锁了一个文件,那它们都在等待对方解锁另一个文件,而这永远不会发生。

#define N 5 

void philosopher(int i)
{
while (true) {
think() ;
take_fork(i) ;
take_fork((i+1)%N) ;
eat() ;
put_fork(i) ;
put_fork((i+1)%N) ;
}
}

上述算法显然是错误的;容易发生饥饿;

对上述算法可作如下改进,它既不会发生死锁,也不会产生饥饿:使用一个二元信号量对调用think之后的5个语句进行保护。在开始拿叉子之前,哲学家先对互斥量mutex执行wait操作。在放回叉子后,他再对mutex执行up操作。从理论上讲,这种解法是可行得。但从实际角度看,这里有性能上的局限:在任何时刻只能有一位哲学家就餐。而5把叉子实际上可以允许两位哲学家同时进餐。

#define N 5  // 哲学家的数目 
#define LEFT (i+N-1)%N // i的左邻居编号
#define RIGHT (i+1)%N // i的右邻居编号
#define THINKING 0 // 哲学家在思考
#define HUNGRY 1 // 哲学家试图拿起叉子
#define EATING 2 // 哲学家就餐
typedef int semaphore ; // 信号量是一种特殊的整型数据
int state[N] ; // 数组用来跟踪记录每位哲学家的状态,全局变量初始化为0,即是说哲学家的初始化状态是THINKING ;
semaphore mutex = 1 ; // 临界区的互斥
semaphore s[N] ; // 每个哲学家一个信号量

void philosopher(int i) { // i:哲学家编号,从0到i-1
while (true) {
think() ;
take_forks(i) ;
eat() ;
put_forks(i) ;
}
}

void take_forks(int i) {
wait(mutex) ; // 进入临界区
state[i] = HUNGRY ; // 记录哲学家i处于饥饿的状态
test(i) ; // 尝试获得2把尺子
signal(mutex) ; // 离开临界区
wait(s[i]) ; // 如果得不到需要的尺子则阻塞
}

void put_forks(int i) {
wait(mutex) ; // 进入临界区
state[i] = THINKING ; // 记录哲学家i已经就餐完毕
test(LEFT) ; // 检查左边的邻居现在可以吃吗
test(RIGHT) ; // 检查右边的邻居现在可以吃吗
signal(mutex) ; // 离开临界区
}

void test(int i) {
if (state[i] == HUNGRY && state[LEFT] != EATING && state[RIGHT] != EATING) {
state[i] = EATING ;
signal(s[i]) ;
}
}
上述算法不仅没有死锁,而且对于任意哲学家的情况都能获得最大的并行度。算法中使用一个数组State跟踪每一个哲学家是在进餐,思考,还是饥饿状态(正在试图拿叉子)。一个哲学家只有在两个邻居都没有进餐时才能进入进餐状态。
课件上给出的算法,使用了管程,而不是互斥信号量来使得其原子性操作; 
void philosopher(int i) {
while(true){
thinking();
dp.pickup(i);
eating();
dp.putdown(i);
}
}

monitor dp {
enum {thinking, hungry, eating} state[5];
condition self[5];

initializationCode() {
for ( int i = 0; i < 5; i++ )
state[i] = thinking;
}
void putdown(int i) {
state[i] = thinking;// test left & right neighbors
test((i+4) % 5);
test((i+1) % 5);
}
void pickup(int i) {
state[i] = hungry; test[i];
if (state[i] != eating)
self[i].wait();
}
void test(int i) {
if ( (state[(i + 4) % 5] != eating) && (state[i] == hungry) &&(state[(i + 1) % 5] != eating)) {
state[i] = eating;
self[i].signal();
}
}
}
Several solutions are possible:
  – Allow only 4 philosophers to be hungry at a time.
  – Allow pickup only if both chopsticks are available. ( Done in critical section )
  – Odd # philosopher always picks up left chopstick 1st, even # philosopher always picks up right chopstick 1st.
原文地址:https://www.cnblogs.com/diyingyun/p/2275010.html