信号量【N-1】+互斥锁【N】,N个哲学家吃饭,避免死锁(所有人拿起左叉,信号量N-1,就可以解决有一个人不会拿起左叉),所有的叉子都是互斥锁

哲学家进餐问题(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哲学家并发运行
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值