死锁
什么是死锁?
简而言之,在并发的环境下,各进程因竞争资源而造成的一种互相等待对方手里的资源,导致各进程都阻塞,都无法向前推进的现象,就是“死锁”
之所以说是“进程”,是因为死锁的概念最初是在讨论进程间的资源竞争时提出来的。后来随着线程的出现,线程间也可能发生死锁,原理是相同的
死锁 饥饿 死循环
- 死锁:各进程互相等待对方手里的资源,导致各进程都阻塞,无法向前推进的现象
- 饥饿:由于长期得不到想要的资源,某进程无法向前推进的现象
- 死循环:某进程执行过程中一直跳不出某个循环的现象。有时是因为程序逻辑 bug 导致的,有时是程序员故意设计的
相同点:都是进程无法顺利向前推进的现象
死锁产生的必要条件
- 互斥条件:进程对所分配到的资源进行排他性控制,即在一段时间内某资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待
进程所请求到的资源不能被其他线程所请求到,其他线程只能等待
- 请求和保持条件:进程已经获得了至少一个资源,但又对其他资源发出请求,而该资源已被其他进程占有,此时该进程的请求被阻塞,但又对自己获得的资源保持不放
请求到至少一个资源的线程对另外的资源进行请求,但是被请求的资源又被其他线程所持有,结果就是该线程的请求被阻塞,同时又对自己持有的资源保持不放
- 不可剥夺条件:进程已获得的资源在未使用完毕之前,不可被其他进程强行剥夺,只能由自己释放
线程持有的资源只能由自己释放,无法被其他线程强行剥夺
- 环路等待条件:存在⼀种进程资源的循环等待链,链中每⼀个进程已获得的资源同时被链中下一个进程所请求,从而造成每个线程的请求都被阻塞同时每个线程都对自己持有的资源保持不放,形成了死锁
!!!注意:发生死锁肯定有循环等待,但是发生循环不一定是死锁
怎么处理死锁?
1.预防死锁
只要打破死锁形成的四个必要条件中的一个就可以去预防死锁
- 打破互斥条件:如果把只能互斥使用的资源改造为允许共享使用,则系统不会进入死锁状态
不是所有的资源都是可以改造成共享的。相反,为了系统安全,这种资源的互斥是有必要的,所以一般不考虑这个条件
- 破坏不可剥夺条件:当进程占有某些资源后又进一步申请其他资源而无法满足时,则该进程要释放自己原来所持有的资源,待以后需要时重新申请
缺点:1.反复的申请和释放资源会增加系统开销,较低系统吞吐量;2.这个方案意味着只要进程暂时得不到某个资源,之前获得的那些资源就都需要放弃,以后再重新申请。如果一直发生这样的情况,就会导致进程饥饿
- 打破请求和条件:
- 静态分配方法:即进程在运行前一次申请完它所需要的全部资源,在它的资源未满足前,不让它投入运行。一旦投入运行后,这些资源就一直归它所有,该进程就不会再请求别的任何资源了
该策略实现起来简单,但也有明显的缺点:有些资源可能只需要用很短的时间,因此如果进程的整个运行期间都一直保持着所有资源,就会造成严重的资源浪费,资源利用率极低。另外,该策略也有可能导致某些进程饥饿
- 打破环路等待条件:实现资源有序分配策略,将系统的所有资源统一编号,所有进程只能采用按序号递增的形式申请资源
2.避免死锁
先来了解一下什么是安全序列!
什么是安全序列?
所谓安全序列,就是指如果系统按照这种序列分配资源,则每个进程都能顺利完成。只要能找出一个安全序列,系统就是安全状态。当然,安全序列可能有多个
银行家算法
银行家算法是荷兰学者 Dijkstra 为银行系统设计的,以确保银行在发放现金贷款时,不会发生不能满足所有客户需要的情况。后来该算法被用在操作系统中,用于避免死锁。
核心思想:在进程提出资源申请时,先预判此次分配是否会导致系统进入不安全状态。如果会进入不安全状态,就暂时不答应这次请求,让该进程先阻塞等待
思考下面这个问题: 系统中有5个进程P0 ~ P4,3种资源 R0 ~ R2,初始数量为(10,5,7),则某一时刻的情况可表示如下:
| 进程 | 最大需求 | 已分配 | 最多还需要 |
|---|---|---|---|
| P0 | (7,5,3) | (0,1,0) | (7,4,3) |
| P1 | (3,2,2) | (2,0,0) | (1,2,2) |
| P2 | (9,0,2) | (3,0,2) | (6,0,0) |
| P3 | (2,2,2) | (2,1,1) | (0,1,1) |
| P4 | (4,3,3) | (0,0,2) | (4,3,1) |
此时资源总数(10,5,7),已分配(7,2,5),剩余可用资源(3,3,2)
思路:
- 尝试找出一个安全序列,也就是依次检查剩余可用资源能否满足各进程的需求
- 发现P1满足需求,则将P1加入安全序列
- P1顺利完成并归还资源,此时剩余可用资源来到(5,3,2)【注意:后面已加入安全序列的进程不再考虑】
- 发现P3满足足需求,则将P3加入安全序列
- P3顺利完成并归还资源,此时剩余可用资源来到(7,4,3)
- ... ...
- 以此类推,共五次循环检查即可将5个进程都加入安全序列中,最终可得一个安全序列。该算法称为安全性算法。可以很方便地用代码实现以上流程,每一轮检查都从编号较小的进程开始检查。实际做题时可以更快速的得到安全序列
逻辑实现
假设系统中有 n 个进程,m 种资源
每个进程在运行前先声明对各种资源的最大需求数,则可用一个 n * m 的矩阵(可用二维数组实现)表示所有进程对各种资源的最大需求数。不妨称为最大需求矩阵 Max,Max[i, j]=K 表示进程 Pi 最多需要 K 个资源Rj。同理,系统可以用一个 n * m 的分配矩阵 Allocation表示对所有进程的资源分配情况。Max – Allocation =Need 矩阵,表示各进程最多还需要多少各类资源
另外,还要用一个长度为m的一维数组 Available 表示当前系统中还有多少可用资源。
某进程Pi向系统申请资源,可用一个长度为m的一维数组 Requesti表示本次申请的各种资源量
可用银行家算法预判本次分配是否会导致系统进入不安全状态:
-
若 Request < Need,便执行下一步,否则认为出错,因为所需要的资源已经大于它所宣布的最大资源
-
若 Request < Available,便执行下一步,否则表示尚无足够资源,该进程需要等待
-
系统试着将资源分配给该进程,并修改相应数据(此时不是真的分配资源,修改数值只是为了做预判)
- Available = Available - Request
- Allocation = Allocation + Request
- Need = Need - Request
-
操作系统执行安全性算法,检查此次资源分配后,系统是否处于安全状态。若安全,才正式分配;否则,恢复相应数据,让进程阻塞等待