先决条件:监控、进程同步
餐饮哲学家问题——N 个哲学家围坐在圆桌旁
- 每个哲学家之间有一根筷子
- 哲学家必须拿起最近的两根筷子才能吃饭
- 哲学家必须先拿起一根筷子,然后再拿起第二根,而不是同时拿起
我们需要一种算法来在多个进程(哲学家)之间分配这些有限的资源(筷子),以便解决方案没有死锁和饥饿。
存在一些解决餐饮-哲学家问题的算法,但它们可能会出现死锁情况。此外,无死锁的解决方案不一定是无饥饿的。由于编程错误,信号量可能导致死锁。仅靠监视器不足以解决这个问题,我们需要带有条件变量的监视器
基于监视器的餐饮哲学家解决方案
我们通过提出餐饮哲学家问题的无死锁解决方案来说明监视器概念。 Monitor 用于控制对状态变量和条件变量的访问。它只告诉何时进入和退出该段。这个解决方案强加了这样的限制,即哲学家只能在两根筷子都可用的情况下才能拿起她的筷子。
为了编码这个解决方案,我们需要区分我们可能会发现哲学家的三种状态。为此,我们引入以下数据结构:
THINKING –当哲学家不想访问任何一个叉子时。
HUNGRY –当哲学家想要进入临界区时。
吃——当哲学家拿到两个叉子时,即他已经进入了这个部分。
哲学家 i 可以设置变量 state[i] = EATING 仅当她的两个邻居不吃东西时
(state[(i+4) % 5] != EATING) 和 (state[(i+1) % 5] != EATING)。
// Dining-Philosophers Solution Using Monitors
monitor DP
{
status state[5];
condition self[5];
// Pickup chopsticks
Pickup(int i)
{
// indicate that I’m hungry
state[i] = hungry;
// set state to eating in test()
// only if my left and right neighbors
// are not eating
test(i);
// if unable to eat, wait to be signaled
if (state[i] != eating)
self[i].wait;
}
// Put down chopsticks
Putdown(int i)
{
// indicate that I’m thinking
state[i] = thinking;
// if right neighbor R=(i+1)%5 is hungry and
// both of R’s neighbors are not eating,
// set R’s state to eating and wake it up by
// signaling R’s CV
test((i + 1) % 5);
test((i + 4) % 5);
}
test(int i)
{
if (state[(i + 1) % 5] != eating
&& state[(i + 4) % 5] != eating
&& state[i] == hungry) {
// indicate that I’m eating
state[i] = eating;
// signal() has no effect during Pickup(),
// but is important to wake up waiting
// hungry philosophers during Putdown()
self[i].signal();
}
}
init()
{
// Execution of Pickup(), Putdown() and test()
// are all mutually exclusive,
// i.e. only one at a time can be executing
for
i = 0 to 4
// Verify that this monitor-based solution is
// deadlock free and mutually exclusive in that
// no 2 neighbors can eat simultaneously
state[i] = thinking;
}
} // end of monitor
以上程序是餐饮哲学家问题的监视器解决方案。
我们还需要声明
condition self[5];
这允许哲学家 i 在饥饿时延迟自己但无法获得她需要的筷子。我们现在可以描述我们对餐饮哲学家问题的解决方案。筷子的分配由监视器Dining Philosophers控制。每个哲学家在开始吃饭之前,必须调用操作pickup()。这一行为可能会导致哲学家进程的暂停。手术成功完成后,哲学家就可以吃饭了。在此之后,哲学家调用 putdown() 操作。因此,哲学家 i 必须按以下顺序调用操作 pick() 和 putdown():
DiningPhilosophers.pickup(i);
...
eat
...
DiningPhilosophers.putdown(i);
很容易证明,该解决方案可确保不会有两个邻居同时进餐,也不会发生死锁。然而,我们注意到哲学家有可能饿死。