# 僵局，第 3 部分：餐饮哲学家

> 原文：<https://github.com/angrave/SystemProgramming/wiki/Deadlock%2C-Part-3%3A-Dining-Philosophers>

## 背景故事

![](img/c17a56237998885b1dd4cd23e86f4c90.jpg)

所以你让你的哲学家坐在桌子周围，想要吃一些意大利面（或其他任何东西），他们真的很饿。每个哲学家本质上是相同的，意味着每个哲学家都有基于另一个哲学家的相同指令集，即你不能告诉每个甚至哲学家做一件事，每个奇怪的哲学家做另一件事。

## 解决方案失败

## 左右僵局

我们做什么？我们来试试一个简单的解决方案

```c
void* philosopher(void* forks){
     info phil_info = forks;
     pthread_mutex_t* left_fork = phil_info->left_fork;
     pthread_mutex_t* right_fork = phil_info->right_fork;
     while(phil_info->simulation){
          pthread_mutex_lock(left_fork);
          pthread_mutex_lock(right_fork);
          eat(left_fork, right_fork);
          pthread_mutex_unlock(left_fork);
          pthread_mutex_unlock(right_fork);
     }
}
```

但这遇到了问题！如果每个人拿起他们的左叉并且正在他们的右叉上等待怎么办？我们已经使该计划陷入僵局。重要的是要注意，死锁不会一直发生，并且随着哲学家数量的增加，这种解决方案死锁的可能性会下降。值得注意的是，最终这个解决方案将陷入僵局，让线程挨饿哪个坏。

## 的 tryLock？更喜欢活锁

所以现在你正在考虑打破其中一个 coffman 条件。我们有

*   相互排斥
*   没有先发制人
*   等等
*   循环等待

好吧，我们不能让两个哲学家同时使用一个分叉，相互排斥是不合适的。在我们当前的简单模型中，一旦他/她掌握它，我们就不能让哲学家放开互斥锁，所以我们现在就把这个解决方案拿出来 - 底部有一些注释。有关此解决方案的页面让我们休息一下等待！

```c
void* philosopher(void* forks){
     info phil_info = forks;
     pthread_mutex_t* left_fork = phil_info->left_fork;
     pthread_mutex_t* right_fork = phil_info->right_fork;
     while(phil_info->simulation){
          pthread_mutex_lock(left_fork);
          int failed = pthread_mutex_trylock(right_fork);
          if(!failed){
               eat(left_fork, right_fork);
               pthread_mutex_unlock(right_fork);
          }
          pthread_mutex_unlock(left_fork);
     }
}
```

现在我们的哲学家拿起左叉并试图抓住右边。如果它可用，他们会吃。如果它不可用，他们将左叉放下并重试。没有死锁！

但有个问题。如果所有的哲学家同时拿起他们的左手，试图抓住他们的权利，把他们的左手放下，拿起他们的左手，试图抓住他们的权利......我们现在已经解决了我们的解决方案！我们可怜的哲学家仍在挨饿，所以让我们给他们一些适当的解决方案。

## 可行的解决方案

## 仲裁员（天真和高级）。

天真的仲裁者解决方案有一个仲裁员（例如互斥）。让每个哲学家都要求仲裁员吃饭。这种解决方案允许一个哲学家一次吃。当他们完成后，另一位哲学家可以请求允许进食。

这可以防止死锁，因为没有循环等待！没有哲学家必须等待任何其他哲学家。

高级仲裁者解决方案是实施一个类，确定哲学家的分叉是否在仲裁员的掌握之中。如果他们是，他们把它们交给哲学家，让他吃，并把分叉拿回来。这有额外的好处，可以让多个哲学家同时吃。

### 问题：

*   这些解决方案很慢
*   他们有一个单点的失败，仲裁员使其成为瓶颈
*   仲裁者也需要公平，并能够在第二个解决方案中确定死锁
*   在实际系统中，仲裁员倾向于将重复的分叉交给那些因为过程调度而吃的哲学家

## 离开桌子（Stallings 的解决方案）

为什么第一个解决方案陷入僵局？那么有 n 个哲学家和 n 个筷子。如果餐桌上只有 1 个 philsopher 怎么办？我们可以陷入僵局吗？没有。

2 个 philsophers 怎么样？ 3？ ......你可以看到它的发展方向。斯托林斯的解决方案说要将哲学家从桌子上移除，直到陷入僵局为止 - 想想桌上哲学家的神奇数量是多少。在实际系统中这样做的方法是通过信号量并让一定数量的哲学家通过。

### Problems:

*   该解决方案需要大量的上下文切换，这对 CPU 来说非常昂贵
*   你需要事先知道资源的数量，才能让那些哲学家知道
*   再次优先考虑已经吃过的过程。

## 部分订购（Dijkstra 的解决方案）

这是 Dijkstra 的解决方案（他是在考试中提出这个问题的人）。为什么第一个解决方案陷入僵局？ Dijkstra 认为最后一个拿起左叉的哲学家（使解决方案陷入僵局）应该选择他的权利。他用数字 1..n 来完成它，并告诉每个哲学家拿起他的较低数字叉。

让我们再次遇到死锁情况。每个人都试图先拿起他们的低号码叉。哲学家 1 获得分叉 1，哲学家 2 获得分叉 2，依此类推，直到我们到达哲学家 n。他们必须在 fork 1 和 n 之间进行选择。 fork 1 已被哲学家 1 所阻挡，所以他们无法拿起那个分叉，这意味着他不会拿起分叉。我们已经打破了循环等待！意味着死锁是不可能的。

### Problems:

*   在获取任何资源之前，哲学家需要按顺序了解资源集。
*   您需要为所有资源定义部分订单。
*   优先考虑已经吃过的哲学家。

## 高级解决方案

还有许多更为先进的解决方案，非详尽列表包括

*   清洁/脏叉（钱德拉/米斯拉解决方案）
*   演员模型（其他消息传递模型）
*   超级仲裁员（复杂的管道）