死锁是进程死锁的简称,是由Dijkstra于1965年研究银行家算法时首先提出来的。它是计算机系统乃至并发程序设计中最难处理的问题之一。 抛开语言,用一个例子来聊一聊死锁问题,聊一聊它是怎么产生的?应该怎么防止?
经典哲学家就餐问题
场景:五个哲学家围在圆桌旁,每个人左手边有一只筷子。每个哲学家必须同时拿到两只筷子才能吃饭。(互斥使用)
问题模型:应用程序中并发线程执行中时,协调处理共享资源。
死锁:如果五个哲学家同时拿起左手边的筷子,就会死锁;
死锁的四个必要条件:
- 互斥条件:一个资源每次只能被一个进程使用(一只筷子只能被一个人用)
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放 (哲学家不拿到第二支筷子,他就一直死等)
- 不剥夺条件 :进程已获得的资源,在末使用完之前,不能强行剥夺(不能抢别人手里的筷子)
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系 (五个哲学家相互等待)
一般来说,只要破坏掉死锁的其中一个必要条件,即可避免死锁。而互斥条件一般比较难以解决,所以我们从另外的三个条件入手。
可能的解决方案:
破坏死锁的请求与保持条件:
- 仅当左右两边筷子都可用时,才允许他拿筷子。也就是说将拿两支筷子变成原子操作。比较简单的办法就是再加一个锁,所以同一个时间点只会有一个哲学家在拿筷子
- 这种方法的好处是简单、易于实现且很安全,缺点却是资源被严重浪费,使进程延迟运行
破坏死锁的不剥夺条件:
- 当哲学家拿了一边的筷子以后,在去拿另一边的筷子的时候,发现无法拿,就放弃已经拿的筷子,以后在重新拿
- 这种方法的缺点就是一个资源在使用一段时间后,它的被迫释放可能会造成前段工作的失效
破坏循环等待条件:
- 让哲学家按照某一种顺序去请求资源,使得在所形成的资源分配图中,不可能再出现环路。
- 给哲学家编号,让奇数号的哲学家先拿右边的筷子,在拿左边的筷子,偶数号的哲学家先拿左边的筷子,在拿右边的筷子
- 让最多4个哲学家能够有机会拿筷子,其中至少有一个哲学家能够拿起两边的筷子,不会产生死锁
- 这种设计很考验技巧,很有可能使得程序的扩展性变得很差。
- 让哲学家按照某一种顺序去请求资源,使得在所形成的资源分配图中,不可能再出现环路。