经典死锁问题 - 哲学家圆桌就餐

 

https://leetcode-cn.com/problems/the-dining-philosophers/

5 个沉默寡言的哲学家围坐在圆桌前,每人面前一盘意面。叉子放在哲学家之间的桌面上。(5 个哲学家,5 根叉子)

所有的哲学家都只会在思考和进餐两种行为间交替。哲学家只有同时拿到左边和右边的叉子才能吃到面,而同一根叉子在同一时间只能被一个哲学家使用。每个哲学家吃完面后都需要把叉子放回桌面以供其他哲学家吃面。只要条件允许,哲学家可以拿起左边或者右边的叉子,但在没有同时拿到左右叉子时不能进食。

假设面的数量没有限制,哲学家也能随便吃,不需要考虑吃不吃得下。

设计一个进餐规则(并行算法)使得每个哲学家都不会挨饿;也就是说,在没有人知道别人什么时候想吃东西或思考的情况下,每个哲学家都可以在吃饭和思考之间一直交替下去。
 

哲学家从 0 到 4 按 顺时针 编号。请实现函数 void wantsToEat(philosopher, pickLeftFork, pickRightFork, eat, putLeftFork, putRightFork):

    philosopher 哲学家的编号。
    pickLeftFork 和 pickRightFork 表示拿起左边或右边的叉子。
    eat 表示吃面。
    putLeftFork 和 putRightFork 表示放下左边或右边的叉子。
    由于哲学家不是在吃面就是在想着啥时候吃面,所以思考这个方法没有对应的回调。

给你 5 个线程,每个都代表一个哲学家,请你使用类的同一个对象来模拟这个过程。在最后一次调用结束之前,可能会为同一个哲学家多次调用该函数。

 

这是操作系统教材中的经典问题,下面引用《操作系统概念,第七版》6.6.3中的话:

三种方法:

# Allow at most four philosophers to be sitting simultaneously at the table.

#Allow a philosopher to pick up her chopsticks only if both chopsticks are available( to do this she must pick them up in a critical sectioin).

#Use a asymmetric solution; that is, an odd philosopher picks up first her left chopstick and then her right chopstick, whereas an even philosopher picks up

her right chopstick and then left chopstick.


以上方法仅仅能做到防止死锁,但是不能保证不会出现某个哲学家一直没有机会进餐, leetcode的该题并没有考察这点。

Finally, any satisfactory solution to the dinning-philosopher problem must guard against the posibility that one of the philosophers will starve to death. A deadlock-free
solution does not necessarily eliminate the possibility of starvation.

 

根据以上方法,在此实现两种:(第二种更好)

1)
使用了一个锁,来锁住大的用于记录chopsticks使用情况的结构体,粒度有些大。
该方法基于上帝视角,记录系统中所有资源的使用情况.当一个哲学家两侧的筷子都没有被使用时才进餐。
   ph0 -- chop0 -- ph1 -- chop1 -- ph2 -- chop2 -- ph3 -- chop3 --  ph4 --  chop4
   ^                                                                                                                ^
   |----------------------------------------------------------------------------------------------|

#pragma once
#include <iostream>
#include <functional>
#include <mutex>
#include <vector>
#include <algorithm>
using namespace std;

 //使用了一个锁,来锁住大的用于记录chop使用情况的结构体,粒度太大。
//该方法基于上帝视角,记录系统中所有资源的使用情况.当一个哲学家两侧的筷子都没有被使用时才进餐。
//    ph0 -- chop0 -- ph1 -- chop1 -- ph2 -- chop2 -- ph3 -- chop3 --  ph4 --  chop4
//     ^                                                                       ^
//     |-----------------------------------------------------------------------|
class DiningPhilosophers {
public:
    DiningPhilosophers() {

    }

   
    void wantsToEat(int philosopher,
        function<void()> pickLeftFork,
        function<void()> pickRightFork,
        function<void()> eat,
        function<void()> putLeftFork,
        function<void()> putRightFork) {

        int rightChopIdx = philosopher;
        int leftChopIdx = philosopher==0 ? 4 : philosopher - 1;

        chopStateLock.lock();
        if (!chopUsed[rightChopIdx] && !chopUsed[leftChopIdx]) {
            //left and right chop are available:
            pickLeftFork();
            pickRightFork();
            chopUsed[rightChopIdx] = true;
            chopUsed[leftChopIdx] = true;

            chopStateLock.unlock();

            eat();

            chopStateLock.lock();
            putLeftFork();
            putRightFork();
            chopUsed[rightChopIdx] = false;
            chopUsed[leftChopIdx] = false;
            chopStateLock.unlock();
        }
        else {
            chopStateLock.unlock();
        }  
    }

private:
    bool chopUsed[5] = {false};
    mutex chopStateLock;
};


 

2)
使用了5个锁,每个筷子一个锁,减小粒度。即 上文中的第三种思路:打破一个死锁条件: circle
    ph0 -- chop0 -- ph1 -- chop1 -- ph2 -- chop2 -- ph3 -- chop3 --  ph4 --  chop4
     ^                                                                                                              ^
     |---------------------------------------------------------------------------------------------|

每个哲学家先get其两侧的筷子中序号小的筷子,例如,ph1,总是先get chop0,然后get chop4,
产生死锁的现象是 ph1已经get到chop0,然后等待被ph4 get到的chop4被释放,同时,ph4此时仅仅持有chop4且在等待chop3
但是, 这种现象是不会发生的,因为,根据规则,当ph4得到chop4时,意味着其已经得到了两个筷子。

//使用了5个锁的方法,每个筷子一个锁,减小粒度
//打破一个死锁条件: circle
//    ph0 -- chop0 -- ph1 -- chop1 -- ph2 -- chop2 -- ph3 -- chop3 --  ph4 --  chop4
//     ^                                                                       ^
//     |-----------------------------------------------------------------------|
//方法: 每个哲学家先get其两侧的筷子中序号小的筷子,例如,ph1,总是先get chop0,然后get chop4,
//产生死锁的现象是 ph0已经get到chop0,然后等待被ph4 get到的chop4被释放,同时,ph4此时仅仅持有chop4且在等待chop3
//,但是, 这种现象是不会发生的,因为,根据规则,当ph4得到chop4时,以为着其已经得到了两个筷子。
class DiningPhilosophers2 {
public:
    DiningPhilosophers2() {
        //chopLocks.resize(5);//编译错误:“std::mutex::mutex(const std::mutex &)”: 尝试引用已删除的函数
                            //看来mutex类不支持复制构造函数
        chopLocks = vector<mutex>(5);
    }


    void wantsToEat(int philosopher,
        function<void()> pickLeftFork,
        function<void()> pickRightFork,
        function<void()> eat,
        function<void()> putLeftFork,
        function<void()> putRightFork) {

        int rightChopIdx = philosopher;
        int leftChopIdx = philosopher == 0 ? 4 : philosopher - 1;
        int minChopIdx = std::min(rightChopIdx, leftChopIdx);
        int maxChopIdx = std::max(rightChopIdx, leftChopIdx);

        chopLocks[minChopIdx].lock();
        chopLocks[maxChopIdx].lock();
        pickLeftFork();
        pickRightFork();
        eat();
        putLeftFork();
        putRightFork();
        chopLocks[maxChopIdx].unlock();
        chopLocks[minChopIdx].unlock();
    }

private:
    vector<mutex> chopLocks;
};

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

First Snowflakes

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值