哲学家进餐问题

引言

哲学家进餐问题(Dining Philosophers Problem)是一个经典的同步问题,经常用来演示多线程或多进程环境下的死锁、饥饿和同步等问题。这一问题由计算机科学家 Edsger Dijkstra 提出,用来说明如何管理多个进程竞争有限资源的情形。

问题描述

假设有五位哲学家围坐在一张圆桌前,每位哲学家面前放着一个盘子和一个叉子。他们的生活方式是交替地思考和进餐,但要进餐时,哲学家需要同时持有他左边和右边的叉子(每位哲学家左手边和右手边各有一只叉子)。当哲学家吃完饭后会放下叉子继续思考。

哲学家进餐问题的关键限制

  • 每个哲学家需要同时持有两只叉子(左边和右边)才能进餐。
  • 当一位哲学家试图进餐时,可能会导致与邻座哲学家竞争叉子的情况。

问题带来的挑战

1、死锁:如果每位哲学家先拿起左手边的叉子,再去拿右手边的叉子,可能所有哲学家都各自持有一只叉子,等待另一只叉子,结果谁也不能进餐。
2、饥饿:某位哲学家可能因为其他哲学家频繁进餐而长时间无法获得两只叉子,导致“饥饿”。
3、并发控制:需要设计一种机制来确保哲学家们可以在不死锁或不饥饿的情况下公平地获得进餐机会。

解决方案

以下是几种经典的解决方案,用于避免死锁或饥饿的问题。
方案 1:引入一个“拿起叉子”的顺序
一个简单的避免死锁的方法是规定哲学家必须以固定顺序拿起叉子。例如,让四位哲学家先拿左边的叉子再拿右边的叉子,但第五位哲学家先拿右边的叉子再拿左边的叉子。这样可以破坏死锁的循环等待条件。
方案 2:最多允许四位哲学家同时拿叉子
在五位哲学家中,最多允许四位哲学家同时拿起一只叉子。这样至少有一位哲学家能进餐(同时拿到两只叉子),确保系统不会陷入死锁。
方案 3:引入一个“服务员”来分配叉子
可以在哲学家和叉子之间引入一个“服务员”,哲学家在拿叉子前先向服务员申请。服务员会控制每次最多只有一位哲学家获得两只叉子,避免资源冲突。每次哲学家吃完后,放下叉子并通知服务员,服务员会将叉子分配给下一位需要的哲学家。

C++ 示例代码(使用互斥锁实现)

下面的代码示例展示了如何用 std::mutex 和 std::thread 来模拟哲学家进餐问题,并采用方案 1 来避免死锁(规定哲学家以固定顺序拿起叉子)。

#include <iostream>
#include <thread>
#include <mutex>
#include <vector>
#include <chrono>

std::mutex forks[5]; // 五把叉子

void philosopher(int id) {
    int left = id;           // 哲学家左边的叉子
    int right = (id + 1) % 5; // 哲学家右边的叉子

    for (int i = 0; i < 3; ++i) { // 哲学家进餐三次
        // 思考
        std::cout << "Philosopher " << id << " is thinking.\n";
        std::this_thread::sleep_for(std::chrono::milliseconds(100));

        // 拿起叉子
        if (id % 2 == 0) { // 偶数编号哲学家先拿左边的叉子
            std::lock_guard<std::mutex> left_fork(forks[left]);
            std::lock_guard<std::mutex> right_fork(forks[right]);
            std::cout << "Philosopher " << id << " is eating.\n";
            std::this_thread::sleep_for(std::chrono::milliseconds(100));
        } else { // 奇数编号哲学家先拿右边的叉子
            std::lock_guard<std::mutex> right_fork(forks[right]);
            std::lock_guard<std::mutex> left_fork(forks[left]);
            std::cout << "Philosopher " << id << " is eating.\n";
            std::this_thread::sleep_for(std::chrono::milliseconds(100));
        }

        // 放下叉子(离开作用域时,锁自动释放)
        std::cout << "Philosopher " << id << " finished eating.\n";
    }
}

int main() {
    std::vector<std::thread> philosophers;

    // 启动 5 个哲学家线程
    for (int i = 0; i < 5; ++i) {
        philosophers.push_back(std::thread(philosopher, i));
    }

    // 等待所有哲学家线程完成
    for (auto& phil : philosophers) {
        phil.join();
    }

    return 0;
}

代码说明

  • 互斥锁数组:std::mutex forks[5] 表示五把叉子,每个叉子有一个互斥锁。
  • 哲学家线程:每个哲学家(线程)试图同时持有左边和右边的叉子才能进餐。
    -** 顺序拿起叉子**:通过 if (id % 2 == 0) 控制,编号为偶数的哲学家先拿左边的叉子,编号为奇数的哲学家先拿右边的叉子,这样破坏了死锁的循环等待条件。

运行结果

Philosopher 0 is thinking.
Philosopher 1 is thinking.
...
Philosopher 0 is eating.
Philosopher 1 is eating.
...
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值