死锁相关问题与哲学家吃饭问题

0 死锁概念

在并发编程中,死锁是一个常见的概念,它是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
举例来说,如下图所示,进程1在已经锁定资源1的情况下,去申请锁定资源2,很不巧,资源2已经被进程2锁定了,所以进程1只能原地阻塞等待,更不巧的是进程2在执行过程中又来申请资源1,同样的道理进程2也只能原地阻塞等待,二者互不相让,所以只能永远等待下去。
在这里插入图片描述

1 死锁产生的必要条件

死锁产生的必要条件有四个,四者必须同时发生:

  1. 互斥条件:进程要求对所分配的资源进行排它性控制,即在一段时间内某资源仅为一进程所占用。
  2. 请求和保持条件:当进程因请求资源而阻塞时,对已获得的资源保持不放。
  3. 不剥夺条件:进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放。
  4. 环路等待条件:在发生死锁时,必然存在一个进程–资源的环形链。

这四点都不难理解,对照上面的例子即可。

2 解决死锁的基本方法

2.1 预防死锁

上面也说过死锁产生的4个必要条件必须同时满足才会发生死锁,预防死锁就是要破坏其中的某几个条件。

  • 资源一次性分配:一次性分配所有资源,这样就不会再有请求了:(破坏请求条件)
  • 只要有一个资源得不到分配,也不给这个进程分配其他的资源:(破坏请保持条件)
  • 可剥夺资源:即当某进程获得了部分资源,但得不到其它资源,则释放已占有的资源(破坏不可剥夺条件)
  • 资源有序分配法:系统给每类资源赋予一个编号,每一个进程按编号递增的顺序请求资源,释放则相反(破坏环路等待条件)

2.2 避免死锁

  • 预防死锁的几种策略,会严重地损害系统性能。因此在避免死锁时,要施加较弱的限制,从而获得 较满意的系统性能。由于在避免死锁的策略中,允许进程动态地申请资源。因而,系统在进行资源分配之前预先计算资源分配的安全性。若此次分配不会导致系统进入不安全的状态,则将资源分配给进程;否则,进程等待。其中最具有代表性的避免死锁算法是银行家算法。
  • 银行家算法:首先需要定义状态和安全状态的概念。系统的状态是当前给进程分配的资源情况。因此,状态包含两个向量Resource(系统中每种资源的总量)和Available(未分配给进程的每种资源的总量)及两个矩阵Claim(表示进程对资源的需求)和Allocation(表示当前分配给进程的资源)。安全状态是指至少有一个资源分配序列不会导致死锁。当进程请求一组资源时,假设同意该请求,从而改变了系统的状态,然后确定其结果是否还处于安全状态。如果是,同意这个请求;如果不是,阻塞该进程知道同意该请求后系统状态仍然是安全的。

2.3 死锁检测

  1. 首先为每个进程和每个资源指定一个唯一的号码;
  2. 然后建立资源分配表和进程等待表。

2.4 死锁解除

当发现有进程死锁后,便应立即把它从死锁状态中解脱出来,常采用的方法有:

  • 剥夺资源:从其它进程剥夺足够数量的资源给死锁进程,以解除死锁状态;
  • 撤消进程:可以直接撤消死锁进程或撤消代价最小的进程,直至有足够的资源可用,死锁状态.消除为止;所谓代价是指优先级、运行代价、进程的重要性和价值等。

3 哲学家吃饭问题

3.1 题面

哲学家吃饭问题是一个很有名的死锁模型,leetcode1226题提供了这道题。题面如下:

5 个沉默寡言的哲学家围坐在圆桌前,每人面前一盘意面。叉子放在哲学家之间的桌面上。(5 个哲学家,5 根叉子)
所有的哲学家都只会在思考和进餐两种行为间交替。哲学家只有同时拿到左边和右边的叉子才能吃到面,而同一根叉子在同一时间只能被一个哲学家使用。每个哲学家吃完面后都需要把叉子放回桌面以供其他哲学家吃面。只要条件允许,哲学家可以拿起左边或者右边的叉子,但在没有同时拿到左右叉子时不能进食。
假设面的数量没有限制,哲学家也能随便吃,不需要考虑吃不吃得下。
设计一个进餐规则(并行算法)使得每个哲学家都不会挨饿;也就是说,在没有人知道别人什么时候想吃东西或思考的情况下,每个哲学家都可以在吃饭和思考之间一直交替下去。
在这里插入图片描述

很显然五个人五把叉子,一个哲学家完整的就餐流程为:拿起一把叉子->拿起另一把叉子->吃->放下一把叉子->放下另一把叉子。显然如果一个哲学家手边的叉子被别人拿走的话,他只能等待,当然在用餐结束前,哲学家不会放开自己的叉子。如果上述这个流程封装成一个函数,由五个线程同时执行,就有可能会出现死锁,比如五个人依次拿起了左手边的叉子,这时就形成一个环路,发生死锁。

3.2 解决方案

一种解决方案就是,规定同一时刻只能由一个人来吃饭,一旦有个人拿起叉子,其他人就不可以拿了,这样问题就解决了,代码如下,只需要使用一个互斥锁保护即可:

class DiningPhilosophers {
    
    
public:
    DiningPhilosophers() {
    
    
        pthread_mutex_init(&mutex,NULL);
    }

    void wantsToEat(int philosopher,
                    function<void()> pickLeftFork,
                    function<void()> pickRightFork,
                    function<void()> eat,
                    function<void()> putLeftFork,
                    function<void()> putRightFork) 
    {
    
    
		pthread_mutex_lock(&mutex);
        pickLeftFork();
        pickRightFork();
        eat();
        putLeftFork();
        putRightFork();
        pthread_mutex_unlock(&mutex);
    }
private:
    pthread_mutex_t mutex;
};

上面的方法还是不够高效,另一种比较高效的方法是对于排号为偶数的哲学家先拿左手边的叉子,后拿右手边的叉子,对于排号为奇数的哲学家先拿右手边的叉子,后拿左手边的叉子,这样可以同时满足两个人的吃饭。代码如下:

class DiningPhilosophers {
    
    
public:
    DiningPhilosophers() 
    {
    
    
        for(int i = 0; i < 5; i++)
            pthread_mutex_init(&fork[i],NULL);    
    }

    void wantsToEat(int philosopher,
                    function<void()> pickLeftFork,
                    function<void()> pickRightFork,
                    function<void()> eat,
                    function<void()> putLeftFork,
                    function<void()> putRightFork) 
    {
    
    
        if(philosopher % 2 == 0)
        {
    
    
            pthread_mutex_lock(&fork[philosopher]);
            pthread_mutex_lock(&fork[(philosopher+1) % 5]);
            pickLeftFork();
            pickRightFork();
        }
        else
        {
    
    
            pthread_mutex_lock(&fork[(philosopher+1) % 5]);
            pthread_mutex_lock(&fork[philosopher]);
            pickLeftFork();
            pickRightFork();           
        }
        eat();
        putLeftFork();
        putRightFork();
        pthread_mutex_unlock(&fork[philosopher]);
        pthread_mutex_unlock(&fork[(philosopher+1) % 5]);
    }
private:
    pthread_mutex_t fork[5];
};

当然哲学家吃饭还有其他很多的解法,这里就不多说了。

猜你喜欢

转载自blog.csdn.net/MoonWisher_liang/article/details/111247469