# 死锁的定义

一组进程中,每个进程都无限等待被该组进程中另一进程所占用的资源,因而永远无法得到的资源,这种现象称为进程死锁,这一组进程就称为死锁进程。

# 活锁和饥饿

  • 活锁:一组进程既无进展也没有阻塞,由于某些条件不满足导致一直在轮询。
  • 饥饿:资源分配策略导致的错误。 ```java public void process_A() { enter_region(&resource_1); enter_region(&resource_2); use_both_resources(); leave_region(&resource_2); leave_region(&resoutce_1); }

public void process_B() { enter_region(&resource_2); enter_region(&resource_1); use_both_resources(); leave_region(&resource_1); leave_region(&resoutce_2); }

  1. <a name="ZcLkQ"></a>
  2. # # 死锁产生的必要条件
  3. 产生死锁需要满足四个条件:
  4. 1. 互斥使用(资源独占):一个资源每次只能给一个进程使用。
  5. 1. 占有且等待(请求和保持):进程在申请新的资源的同时保持对原有资源的占用。
  6. 1. 不可抢占:资源申请者不能强行的从资源占有者手中夺取资源,资源只能等待占有者自愿释放。
  7. 1. 循环等待:多个进程组成一个环路,环路中的每个进程都在等待下一个进程所占用的资源。
  8. <a name="ZDnbA"></a>
  9. # # 资源分配图
  10. 系统由若干类资源构成:
  11. - 资源类:某一类资源称为一个资源类,用方框表示;
  12. - 资源实例:每个资源类中包含若干个同种资源,称为资源实例;
  13. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/2323967/1651943459791-10277555-67ca-49a4-be53-7b664ff1bb1a.png#clientId=uba15203d-b6ef-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=281&id=ub7c82b04&margin=%5Bobject%20Object%5D&name=image.png&originHeight=610&originWidth=1308&originalType=binary&ratio=1&rotation=0&showTitle=true&size=83033&status=done&style=stroke&taskId=uf2d8cd51-434d-4d86-870a-10c96649d2d&title=%E8%B5%84%E6%BA%90%E5%88%86%E9%85%8D%E5%9B%BE&width=603 "资源分配图")<br />如果资源分配图中没有环路,则系统中没有死锁,如果图中存在环路则系统中可能存在死锁。
  14. <a name="SHF8v"></a>
  15. ## | 资源分配图的化简
  16. 1. 找一个非孤立、且只有分配边的进程结点,去掉分配边,将其变为孤立结点。
  17. 1. 再把相应的资源分配给一个等待该资源的进程,即将该进程的申请边变为分配边。
  18. 如果一个图可完全化简(所有的资源和进程都变成孤立的点),则不会产生死锁;如果一个图不可完全化简(即图中还有边存在),则会产生死锁。
  19. <a name="zdJJX"></a>
  20. # # 死锁预防
  21. 在设计系统时,通过确定资源分配算法,排除发生死锁的可能性。<br />只要防止产生死锁的四个必要条件中的任何一个条件发生即可。
  22. <a name="leYqF"></a>
  23. ## | 破坏“互斥使用”条件
  24. 把独占资源变为共享资源。例如 SPOOLing 技术把所有用户进程的输出都送入输出井,然后再由输出进程完成打印工作,而输出井在磁盘上,为共享设备。这样,SPOOLing技术就把打印机等独占设备改造成立共享设备。
  25. <a name="NS1ab"></a>
  26. ## | 破坏“占有且等待”条件
  27. - 实现方案一:要求每个进程在运行前必须一次性申请它所要求的所有资源,且仅当该进程所要资源均可满足时才给予一次性分配。
  28. - 问题:资源利用率低,会出现饥饿现象。
  29. - 实现方案二:在允许进程动态申请资源前提下规定,一个进程在申请新的资源不能立即得到满足而变为等待状态之前,必须释放已占有的全部资源,若需要再重新申请。
  30. <a name="SIB5Z"></a>
  31. ## | 破坏“不可抢占”条件
  32. 当一个进程申请的资源被其他进程占用时,可以通过操作系统抢占这一资源(两个进程的优先级不同)。
  33. > 局限性:适用于状态易于保存和恢复的资源,例如内存。
  34. <a name="Eq9HX"></a>
  35. ## | 破坏“循环等待”条件
  36. 通过定义资源类型的线性顺序实现。
  37. 把系统中的所有资源编号,进程在申请资源时必须严格按照资源编号的递增次序进行,否则操作系统不予分配。
  38. > 通常会按照资源使用的频繁编号。
  39. <a name="efIvy"></a>
  40. # # 死锁避免
  41. 在系统运行的过程中,对进程发出的每一个系统能够满足的资源申请进行动态检查,并根据检查结果决定是否分配资源,若分配后系统发生死锁或可能发生死锁,则不予分配,否则予以分配。
  42. <a name="eFruu"></a>
  43. ## | 安全状态
  44. 如果没有死锁发生,并且即使所有进程突然请求对资源的最大需求,也仍然存在某种调度次序能够使得每一个进程运行完毕,则称该状态是安全的。<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/2323967/1651945621149-11d8ae46-663b-4094-ac30-ecb3ed3f782f.png#clientId=uba15203d-b6ef-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=193&id=u885f07cc&margin=%5Bobject%20Object%5D&name=image.png&originHeight=440&originWidth=1534&originalType=binary&ratio=1&rotation=0&showTitle=true&size=81319&status=done&style=stroke&taskId=ua4b3b6ec-55b1-4c67-aee2-995b243e7f8&title=Has%20%E8%A1%A8%E7%A4%BA%E5%B7%B2%E6%8B%A5%E6%9C%89%E7%9A%84%E8%B5%84%E6%BA%90%E6%95%B0%EF%BC%8CMax%20%E8%A1%A8%E7%A4%BA%E6%80%BB%E5%85%B1%E9%9C%80%E8%A6%81%E7%9A%84%E8%B5%84%E6%BA%90%E6%95%B0%EF%BC%8CFree%20%E8%A1%A8%E7%A4%BA%E8%BF%98%E5%8F%AF%E4%BB%A5%E4%BD%BF%E7%94%A8%E7%9A%84%E8%B5%84%E6%BA%90%E6%95%B0%E3%80%82&width=672 "Has 表示已拥有的资源数,Max 表示总共需要的资源数,Free 表示还可以使用的资源数。")<br />例如从图 a 开始出发,先让 B 拥有所需的所有资源(图b),运行结束后释放 B,此时 Free 变为5(图c);接着以同样的方式运行 C 和 A,使得所有进程都能成功运行,因此可以称图 a 是安全状态。
  45. <a name="LvGN4"></a>
  46. ## | 不安全状态
  47. 如下图所示,图 b 即为不安全状态,因为它不能满足让后续的进程运行完毕。<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/2323967/1651946699994-64d77d60-93d1-43fb-b91d-8d3305cb8295.png#clientId=uba15203d-b6ef-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=203&id=u367b74a2&margin=%5Bobject%20Object%5D&name=image.png&originHeight=458&originWidth=1554&originalType=binary&ratio=1&rotation=0&showTitle=false&size=74455&status=done&style=stroke&taskId=ub210fbfe-ae89-400f-8d6c-cd1b1c66f5c&title=&width=688)
  48. <a name="CsNej"></a>
  49. ## | 银行家算法
  50. <a name="YPFbT"></a>
  51. ### * 单个资源条件
  52. 一个小城镇的银行家,他向一群客户分别承诺了一定的贷款额度,算法要做的是判断对请求的满足是否会进入不安全状态,如果是,就拒绝请求;否则予以分配。<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/2323967/1651946802189-823937cb-2847-40ab-a1fb-4436c5b66775.png#clientId=uba15203d-b6ef-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=259&id=uc559ad4b&margin=%5Bobject%20Object%5D&name=image.png&originHeight=546&originWidth=1294&originalType=binary&ratio=1&rotation=0&showTitle=false&size=75486&status=done&style=stroke&taskId=u84c966d3-10c4-4fd0-be15-63b51d31b32&title=&width=614)<br />上图中,图 c 是一个不安全的状态。因为无论如何都不能满足任何一个客户的请求,所以算法会拒绝之前的请求,避免进入不安全状态。
  53. <a name="S3Y1j"></a>
  54. ### * 多个资源条件
  55. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/2323967/1651946937538-f0ec07fa-9de9-44d1-be81-22adf1829afa.png#clientId=uba15203d-b6ef-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=321&id=u8146c886&margin=%5Bobject%20Object%5D&name=image.png&originHeight=642&originWidth=1048&originalType=binary&ratio=1&rotation=0&showTitle=false&size=110624&status=done&style=stroke&taskId=u72445a1c-1243-4090-af71-a07582d2a88&title=&width=524)<br />上图中有五个进程,四个资源。左边的图表示已经分配的资源,右边的图表示还需要分配的资源。最右边的 E、P 以及 A 为三个向量,分别表示:总资源、已分配资源以及可用资源。例如 A=(1020),表示 4 个资源分别还剩下 1/0/2/0。<br />检查一个状态是否安全的算法如下:
  56. - 查找右边的矩阵是否存在一行小于等于向量 A。如果不存在这样的行,那么系统将会发生死锁,状态是不安全的。
  57. - 如果存在这样一行,将该进程标记为终止,并将其左图中的已分配资源加到 A 中。
  58. - 重复以上两步,直到所有进程都标记为终止,则状态时安全的。
  59. 如果一个状态不是安全的,算法需要拒绝进入这个状态。
  60. <a name="T9x4q"></a>
  61. # # 死锁检测与解除
  62. - 允许死锁的发生,但是操作系统会不断监视系统进展情况,判断死锁是否真的发生。
  63. - 一旦死锁发生则采取专门的措施,解除死锁并以最小的代价恢复操作系统运行。
  64. > 检测时机:
  65. > 1. 当进程由于资源请求不满足而等待时检测死锁
  66. > 1. 定时检测
  67. > 1. 系统资源利用率下降时检测死锁
  68. <a name="T8UuD"></a>
  69. ## | 死锁检测
  70. <a name="VLwz0"></a>
  71. ### * 每种类型单个资源的死锁检测
  72. 判断资源分配图是否满足环路等待条件,具体来说是通过检测有向图是否存在环来实现,从一个节点出发进行深度优先搜索,对访问过的节点进行标记,如果访问了已经标记的节点,就表示有向图存在环,也就是检测到死锁的发生。<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/2323967/1651947660949-4652bbe9-d4a1-425e-9222-d00bf1b5c545.png#clientId=uba15203d-b6ef-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=310&id=u0cd5ae8e&margin=%5Bobject%20Object%5D&name=image.png&originHeight=746&originWidth=1410&originalType=binary&ratio=1&rotation=0&showTitle=false&size=106367&status=done&style=stroke&taskId=u20b1a0ca-d9cf-4ef3-b7af-0ad1839a7b2&title=&width=585)
  73. <a name="bDjd6"></a>
  74. ### * 每种类型多个资源的死锁检测
  75. 同多个资源条件时的银行家算法。<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/2323967/1651947737970-e02af354-4b60-462c-b5fd-4ddd1bedf744.png#clientId=uba15203d-b6ef-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=257&id=u7f1026fe&margin=%5Bobject%20Object%5D&name=image.png&originHeight=556&originWidth=1030&originalType=binary&ratio=1&rotation=0&showTitle=false&size=86836&status=done&style=stroke&taskId=u4f7e4210-eb51-4d60-a71c-993e096da1d&title=&width=477)
  76. <a name="rNxEG"></a>
  77. ## | 死锁解除
  78. - 利用撤销所有死锁进程解除
  79. - 利用抢占解除
  80. - 利用回滚再启动解除
  81. - 通过杀死进程解除
  82. <a name="VZq52"></a>
  83. # # 鸵鸟算法
  84. > 鸵鸟:把头埋在沙子里,假装根本没发生问题。
  85. 因为解决死锁问题的代价很高,因此鸵鸟策略这种不采取任务措施的方案会获得更高的性能。当发生死锁时不会对用户造成多大影响,或发生死锁的概率很低,可以采用鸵鸟策略。<br />大多数操作系统,包括 Unix,Linux 和 Windows,处理死锁问题的办法仅仅是忽略它。
  86. <a name="kzn7k"></a>
  87. # # 哲学家进餐问题
  88. 五个哲学家围着一张圆桌,每个哲学家面前放着食物。哲学家的生活有两种交替活动:吃饭以及思考。当一个哲学家吃饭时,需要先拿起自己左右两边的两根筷子,并且一次只能拿起一根筷子。<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/2323967/1651948540730-4b848d38-ed41-4cec-91e6-9bce16c58bb0.png#clientId=uba15203d-b6ef-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=317&id=u29fd6e1f&margin=%5Bobject%20Object%5D&name=image.png&originHeight=320&originWidth=370&originalType=binary&ratio=1&rotation=0&showTitle=false&size=51909&status=done&style=stroke&taskId=u0f4e9fab-bf7a-4fd4-8048-414f1521a0f&title=&width=367)<br />错误解法:如果所有的哲学家同时拿起了左边的筷子,那么所有哲学家都没有办法拿起右边的筷子,导致死锁。
  89. ```java
  90. #define N 5
  91. void philosopher(int i) {
  92. while(TRUE) {
  93. think();
  94. take(i); // 拿起左边的筷子
  95. take((i+1)%N); // 拿起右边的筷子
  96. eat();
  97. put(i);
  98. put((i+1)%N);
  99. }
  100. }

防止死锁发生的解法:

  • 最多允许 4 个哲学家同时坐在桌子周围。
  • 仅当一个哲学家左右两边的筷子都可以使用时,才允许他拿筷子。
  • 给所有的哲学家编号,奇数哲学家必须首先拿左边的筷子,偶数哲学家反之。

    # 参考

  1. CS-Note:死锁
  2. 北京大学操作系统原理(Operating Systems)
  3. Modern operating systems