哲学家进餐问题(Dining Philosophers Problem)是操作系统/并发编程里的经典同步问题。它模拟了多个哲学家共用叉子吃饭,本质是一个死锁与资源竞争问题。
🧠 问题简述
- 有 5 个哲学家坐在圆桌上,每人两边各一把叉子。
- 吃饭需要同时拿到 左边和右边的叉子。
- 如果每个哲学家都拿起左边的叉子,就会死锁——没人能拿到第二把叉子。
✅ 常见解决方案(避免死锁)
方法一:限制最多只能有 4 个哲学家同时尝试吃饭
因为最多只有 5 把叉子,如果有一个人不参与,就能打破“环路等待”的死锁条件。
✅ 示例代码(基于 pthread、mutex、信号量)
我们用 C 写一个简化的实现:
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>
#define N 5 // 哲学家数量
pthread_mutex_t forks[N]; // 每个叉子的互斥锁
sem_t limit; // 最多允许 N-1 个哲学家同时尝试吃饭
void *philosopher(void *arg) {
int id = *(int *)arg;
int left = id; // 左边叉子编号
int right = (id + 1) % N; // 右边叉子编号
while (1) {
printf("哲学家 %d 正在思考...\n", id);
sleep(1); // 思考一会
sem_wait(&limit); // 限制最多同时 N-1 个哲学家吃饭
pthread_mutex_lock(&forks[left]);
pthread_mutex_lock(&forks[right]);
// 吃饭中
printf("🍴 哲学家 %d 正在吃饭!\n", id);
sleep(1);
// 放下叉子
pthread_mutex_unlock(&forks[right]);
pthread_mutex_unlock(&forks[left]);
sem_post(&limit); // 吃完放开限制
printf("哲学家 %d 吃完了,开始思考...\n", id);
sleep(1);
}
return NULL;
}
int main() {
pthread_t threads[N];
int ids[N];
// 初始化叉子和限制信号量
for (int i = 0; i < N; i++)
pthread_mutex_init(&forks[i], NULL);
sem_init(&limit, 0, N - 1); // 最多 N-1 哲学家可尝试吃饭
// 创建哲学家线程
for (int i = 0; i < N; i++) {
ids[i] = i;
pthread_create(&threads[i], NULL, philosopher, &ids[i]);
}
// 等待线程结束(实际上不会,因为是死循环)
for (int i = 0; i < N; i++)
pthread_join(threads[i], NULL);
return 0;
}
✅ 运行说明
- 每个哲学家会先思考 → 再尝试吃饭。
sem_wait(&limit)
保证同时最多 4 人尝试拿叉子,避免所有人各持一把 → 死锁。pthread_mutex_lock
用于互斥访问“叉子”。
✅ 其他解决方式(了解即可)
方法 | 简要说明 |
---|---|
拿奇偶叉子顺序反转 | 偶数哲学家先拿左叉,奇数先拿右叉,打破资源获取顺序一致性 |
使用全局锁 | 每次吃饭都加一个全局大锁,简单粗暴但降低并发 |
状态机+条件变量 | 高级做法,定义哲学家状态(思考/饥饿/吃饭)+精细调度,参考 Hoare 提出的方法 |
🧠 小总结
-
哲学家进餐问题考察 死锁的 4 个条件:互斥、占有等待、不剥夺、循环等待。
-
关键在于:打破循环等待或占有等待条件。
-
最实用方式之一:信号量限制进入人数 + 互斥锁控制资源
-
std::thread
线程 -
std::mutex
互斥锁(叉子) -
std::semaphore
(C++20 支持)限制最大吃饭人数,避免死锁
✅ C++ 实现(基于 C++20)
#include <iostream>
#include <thread>
#include <mutex>
#include <semaphore>
#include <vector>
#include <chrono>
using namespace std;
const int N = 5; // 哲学家数量
mutex forks[N]; // 每把叉子一个互斥锁
counting_semaphore<N - 1> limit(N - 1); // 限制最多 N-1 哲学家同时尝试吃饭
void philosopher(int id) {
int left = id;
int right = (id + 1) % N;
while (true) {
// 思考
cout << "哲学家 " << id << " 正在思考..." << endl;
this_thread::sleep_for(chrono::seconds(1));
// 尝试进入吃饭区域(最多 N-1 人)
limit.acquire();
// 拿叉子(互斥)
forks[left].lock();
forks[right].lock();
// 吃饭
cout << "🍴 哲学家 " << id << " 正在吃饭!" << endl;
this_thread::sleep_for(chrono::seconds(1));
// 放下叉子
forks[right].unlock();
forks[left].unlock();
// 离开吃饭区域
limit.release();
cout << "哲学家 " << id << " 吃完了,继续思考...\n" << endl;
this_thread::sleep_for(chrono::seconds(1));
}
}
int main() {
vector<thread> philosophers;
for (int i = 0; i < N; ++i) {
philosophers.emplace_back(philosopher, i);
}
for (auto& t : philosophers) {
t.join(); // 实际上这不会结束
}
return 0;
}
✅ 编译方式(需要支持 C++20):
g++ -std=c++20 dining.cpp -lpthread -o dining
如果你的编译器暂时不支持 std::semaphore
,可以用一个 std::counting_semaphore
的自定义实现(或者用信号量库 like semaphore.h
适配)。
✅ 输出示例(随机调度,每次跑略有不同):
哲学家 0 正在思考...
哲学家 1 正在思考...
哲学家 2 正在思考...
...
🍴 哲学家 2 正在吃饭!
🍴 哲学家 4 正在吃饭!
哲学家 3 吃完了,继续思考...
✅ 小结:C++ 哲学家进餐怎么做?
部件 | 用途 |
---|---|
std::mutex forks[N] | 控制每个叉子的独占访问 |
std::semaphore limit(N-1) | 限制最多同时尝试吃饭的人数,防死锁 |
std::thread | 哲学家并发运行 |