进程与线程 IV(死锁)


一、死锁的概念

【总结】:

1. 死锁的定义

在多道程序系统中,由于多个进程的并发执行,改善了系统资源的利用率并提高了系统的处理能力。然而,多个进程的并发执行也带来了新的问题——死锁。所谓死锁,是指多个进程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都将无法向前推进。

在计算机系统中存在该情况。例如,某计算机系统中只有一台打印机和一台输入设备,进程 P1 正占用输入设备,同时又提出使用打印机的请求,但此时打印机正被进程 P2 所占用,而 P2 在未释放打印机之前,又提出请求使用正被 P1 占用的输入设备。这样,两个进程相互无休止地等待下去,均无法继续执行,此时两个进程陷入死锁状态。

2. 死锁、饥饿、死循环的区别

  • 死锁:各进程互相等待对方手里的资源,导致各进程都阻塞,无法向前推进的现象。

  • 饥饿:由于长期得不到想要的资源,某进程无法向前推进的现象。比如:在短进程优先(SPF)算法中,若有源源不断的短进程到来,则长进程将一直得不到处理机,从而发生长进程 “饥饿” 。

  • 死循环:某进程执行过程中一直跳不出某个循环的现象。有时是因为程序逻辑 bug 导致的,有时是程序员故意设计的。

3. 死锁产生的原因

1)系统资源的竞争

通常系统中拥有的不可剥夺资源,其数量不足以满足多个进程运行的需要,使得进程在运行过程中,会因争夺资源而陷入僵局,如磁带机、打印机等。只有对不可剥夺资源的竞争才可能产生死锁,对可剥夺资源的竞争是不会引起死锁的。

2)进程推进顺序非法

进程在运行过程中,请求和释放资源的顺序不当,也同样会导致死锁。例如,并发进程 P1 , P2 分别保待了资源 R1 , R2 ,而进程 P1 申请资源 R2 、进程 P2 申请资源 R1 时,两者都会因为所需资源被占用而阻塞,于是导致死锁。

3)信号量使用不当

信号量使用不当也会造成死锁。进程间彼此相互等待对方发来的消息,也会使得这些进程间无法继续向前推进。例如,进程 A 等待进程 B 发的消息,进程 B 又在等待进程 A 发的消息,可以看出进程 A 和 B 不是因为竞争同一资源,而是在等待对方的资源导致死锁。

如生产者-消费者问题中,如果实现互斥的 P 操作在实现同步的 P 操作之前,就有可能导致死锁。(可以把互斥信号量、同步信号量也看做是一种抽象的系统资源)

总之,对不可剥夺资源的不合理分配,可能导致死锁。

4. 死锁产生的必要条件

直观上看,循环等待条件似乎和死锁的定义一样,其实不然。按死锁定义构成等待环所要求的条件更严,它要求 Pi 等待的资源必须由 Pi+1 来满足,而循环等待条件则无此限制。

例如,系统中有两台输出设备,P0 占有一台,PK 占有另一台,且 K 不属于集合 {0,1, …,n} 。Pn 等待一台输出设备,它可从P0 获得,也可能从 PK 获得。因此,虽然 Pn , P0 和其他一些进程形成了循环等待圈,但 PK 不在圈内,若 PK 释放了输出设备,则可打破循环等待,如下图右所示。因此循环等待只是死锁的必要条件。

下图左为循环等待,下图右为条件但无死锁。简单来说就是死锁一定循环等待,循环等待未必死锁。

5. 死锁的处理策略

为使系统不发生死锁,必须设法破坏产生死锁的 4 个必要条件(互斥、不剥夺、请求和保持、循环等待)之一,或允许死锁产生,但当死锁发生时能检测出死锁,并有能力实现恢复。

  • 死锁预防(静态策略)。设置某些限制条件,破坏产生死锁的 4 个必要条件中的一个或几个。

  • 避免死锁(动态策略)。在资源的动态分配过程中,用某种方法防止系统进入不安全状态,从而避免死锁(银行家算法)。

  • 死锁的检测及解除。无须采取任何限制性措施,允许进程在运行过程中发生死锁。通过系统的检测机构及时地检测出死锁的发生,然后采取某种措施解除死锁。

预防死锁和避免死锁都属于事先预防策略,预防死锁的限制条件比较严格,实现起来较为简单,但往往导致系统的效率低,资源利用率低;避免死锁的限制条件相对宽松,资源分配后需要通过算法来判断是否进入不安全状态,实现起来较为复杂。

死锁的几种处理策略的比较见下表:

资源分配策略各种可能模式主要优点主要缺点
死锁预防保守,宁可资源闲置一次请求所有资源,资源剥夺,资源按序分配适用于突发式处理的进程,不必进行剥夺效率低,进程初始化时间延长;剥夺次数过多;不便灵活申请新资源
死锁避免是“预防”和“检测”的折中(在运行时判断是否可能死锁)寻找可能的安全允许顺序不必进行剥夺必须知道将来的资源需求;进程不能被长时间阻塞
死锁检测宽松,只要允许就分配资源定期检查死锁是否已经发生不延长进程初始化时间,允许对死锁进行现场处理通过剥夺解除死锁,造成损失

二、死锁预防

【总结】:

死锁的产生必须满足四个必要条件,只要其中一个或者几个条件不满足,死锁就不会发生。防止死锁的发生只需破坏死锁产生的 4 个必要条件之一即可。

1. 破坏互斥条件

互斥条件:只有对必须互斥使用的资源的争抢才会导致死锁。

如果把只能互斥使用的资源改造为允许共享使用,则系统不会进入死锁状态。比如:SPOOLing 技术。操作系统可以采用 SPOOLing 技术把独占设备在逻辑上改造成共享设备。比如,用 SPOOLing 技术将打印机改造为共享设备…

该策略的缺点:并不是所有的资源都可以改造成可共享使用的资源。并且为了系统安全,很多地方还必须保护这种互斥性。因此,很多时候都无法破坏互斥条件。

2. 破坏不剥夺条件

不剥夺条件:进程所获得的资源在未使用完之前,不能由其他进程强行夺走,只能主动释放。

破坏不剥夺条件:

  • 方案一:当某个进程请求新的资源得不到满足时,它必须立即释放保持的所有资源,待以后需要时再重新申请。也就是说,即使某些资源尚未使用完,也需要主动释放,从而破坏了不可剥夺条件。
  • 方案二:当某个进程需要的资源被其他进程所占有的时候,可以由操作系统协助,将想要的资源强行剥夺。这种方式一般需要考虑各进程的优先级(比如:剥夺调度方式,就是将处理机资源强行剥夺给优先级更高的进程使用)

该策略的缺点:

  • 实现起来比较复杂。
  • 释放已获得的资源可能造成前一阶段工作的失效。因此这种方法一般只适用于易保存和恢复状态的资源,如 CPU 的寄存器及内存资源, 一般不能用于打印机之类的资源。
  • 反复地申请和释放资源会增加系统开销,降低系统吞吐量。
  • 若采用方案一,意味着只要暂时得不到某个资源,之前获得的那些资源就都需要放弃,以后再重新申请。如果一直发生这样的情况,就会导致进程饥饿。

3. 破坏请求并保持条件

请求和保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源又被其他进程占有,此时请求进程被阻塞,但又对自己已有的资源保持不放。

可以采用静态分配方法,即进程在运行前一次申请完它所需要的全部资源,在它的资源未满足前,不让它投入运行。一旦投入运行后,这些资源就一直归它所有,该进程就不会再请求别的任何资源了。

该策略实现起来简单,但也有明显的缺点:有些资源可能只需要用很短的时间,因此如果进程的整个运行期间都一直保持着所有资源,就会造成严重的资源浪费,资源利用率极低。另外,该策略也有可能导致某些进程饥饿,由于个别资源长期被其他进程占用时,将致使等待该资源的进程迟迟不能开始运行。

4. 破坏循环等待条件

循环等待条件:存在一种进程资源的循环等待链,链中的每一个进程已获得的资源同时被下一个进程所请求。

可采用顺序资源分配法。首先给系统中的资源编号,规定每个进程必须按编号递增的顺序请求资源,同类资源(即编号相同的资源)一次申请完。也就是说,只要进程提出申请分配资源 Ri ,则该进程在以后的资源申请中就只能申请编号大于 Ri 的资源。

原理分析:一个进程只有已占有小编号的资源时,才有资格申请更大编号的资源。按此规则,已持有大编号资源的进程不可能逆向地回来申请小编号的资源,从而就不会产生循环等待的现象。

三、死锁避免

避免死锁同样属于事先预防策略,但并不是事先采取某种限制措施破坏死锁的必要条件,而是在资源动态分配过程中,防止系统进入不安全状态,以避免发生死锁。这种方法所施加的限制条件较弱,可以获得较好的系统性能。

1. 系统安全状态

避免死锁的方法中,允许进程动态地申请资源,但系统在进行资源分配之前,应先计算此次分配的安全性。若此次分配不会导致系统进入不安全状态,则允许分配;否则让进程等待。

所谓安全状态,是指系统能按某种进程推进顺序 (P1, P2, …, Pn) 为每个进程 Pi 分配其所需的资源,直至满足每个进程对资源的最大需求,使每个进程都可顺序完成。此时称 P1, P2, …, Pn 为安全序列,当然,安全序列可能有多个。

若如果分配了资源之后,系统无法找到一个安全序列,则称系统处于不安全状态。这就意味着之后可能所有进程都无法顺利的执行下去。当然,如果有进程提前归还了一些资源,那系统也有可能重新回到安全状态,不过我们在分配资源之前总是要考虑到最坏的情况。

并非所有的不安全状态都是死锁状态,但当系统进入不安全状态后,便可能进入死锁状态;反之,只要系统处于安全状态,系统便可避免进入死锁状态。

如果系统处于安全状态,就一定不会发生死锁。如果系统进入不安全状态,就可能发生死锁(处于不安全状态未必就是发生了死锁,但发生死锁时一定是在不安全状态)。因此可以在资源分配之前预先判断这次分配是否会导致系统进入不安全状态,以此决定是否答应资源分配请求。这也是 “银行家算法” 的核心思想。

2. 银行家算法

银行家算法是荷兰学者 Dijkstra 为银行系统设计的,以确保银行在发放现金贷款时,不会发生不能满足所有客户需要的情况。后来该算法被用在操作系统中,用于避免死锁。

核心思想:在进程提出资源申请时,先预判此次分配是否会导致系统进入不安全状态。如果会进入不安全状态,就暂时不答应这次请求,让该进程先阻塞等待。

1)数据结构描述

假设系统中有 n 个进程,m 种资源,在银行家算法中需要定义下面四个数据结构:

  • 可利用资源向量 Available:含有 m 个元素的数组,其中每个元素代表一类可用的资源数目。Available[j] = K 表示系统中有 K 个 Rj 类资源可用。

  • 最大需求矩阵 Max:n×m 矩阵,定义系统中 n 个进程中的每个进程对 m 类资源的最大需求。简单来说,一行代表一个进程,一列代表一类资源。Max[i,j] = K 表示进程 Pi 需要 Rj 类资源的最大数目为 K 。

  • 分配矩阵 Allocation:n×m 矩阵,定义系统中每类资源当前已分配给每个进程的资源数。 Allocation[i,j] = K 表示进程 Pi 当前已分得 Rj 类资源的数目为 K 。

  • 需求矩阵 Need:n×m 矩阵,表示每个进程接下来最多还需要多少资源。Need[i,j] = K 表示进程 Pi 还需要 Rj 类资源的数目为 K 。

上述三个矩阵间存在下述关系:Need = Max - Allocation 。一般情况下,在银行家算法的题目中,Max 矩阵和 Allocation 矩阵是已知条件,而求出 Need 矩阵是解题的第一步。

2)安全性算法

设置工作向量 Work 有 m 个元素,表示系统中的剩余可用资源数目。在执行安全性算法开始时,Work = Available 。

  • ① 初始时安全序列为空。

  • ② 从 Need 矩阵中找出符合下面条件的行:该行对应的进程不在安全序列中,而且该行小于或等于 Work 向量,找到后,把对应的进程加入安全序列;若找不到,则执行步骤 ④ 。

  • ③ 进程 Pi 进入安全序列后,可顺利执行,直至完成,并释放分配给它的资源,因此应执行 Work = Work + Allocation[i] ,其中 Allocation[i] 表示进程 Pi 代表的在 Allocation 矩阵中对应的行,返回步骤 ② 。

  • ④ 若此时安全序列中已有所有进程,则系统处于安全状态,否则系统处于不安全状态。

【举例】:假定系统中有 5 个进程 {P0 , P1 , P2 , P3 , P4} 和三类资源 {A, B, C} ,各种资源的数量分别为 10, 5, 7,在 T0 时刻的资源分配情况见下表。

最大需求矩阵 Max :定义系统中 5 个进程中的每个进程对 3 类资源的最大需求。

MaxP0P1P2P3P4
A73924
B52023
C32223

分配矩阵 Allocation:定义系统中每类资源当前已分配给每个进程的资源数。

AllocationP0P1P2P3P4
A02320
B10010
C00212

可利用资源向量 Available:含有 3 个元素的数组,其中每个元素代表一类可用的资源数目。

ABC
332

在执行安全性算法开始时,工作向量 Work(表示系统中的剩余可用资源数目)= Available = (3, 3, 2) 。

(a)从题目中我们可以提取 Max 矩阵和 Allocation 矩阵,这两个矩阵相减(Max - Allocation,对应元素相减)可得到需求矩阵 Need(表示每个进程接下来最多还需要多少资源):

NeedP0P1P2P3P4
A71604
B42013
C32011

(b)然后,将 Work 向量 (3, 3, 2) 与 Need 矩阵的各列进行比较,找出比 Work 向量小的列(各元素均要小于)。

例如:初始时 P1 的 (1, 2, 2) 与 P3 的 (0, 1, 1) 比 Work 向量 (3, 3, 2) 小,这里我们选择 P1(也可选择 P3)暂时加入安全序列。

(c)P1 得到了所有的需求,因此释放 P1 所占的资源,即把 P1 进程对应的 Allocation 矩阵中的那一列与 Work 向量相加,即此时:Work = (3, 3, 2) + (2, 0, 0) = (5, 3, 2) 。

此时需求矩阵 Need 更新为(去掉了 P1 对应的一列):

NeedP0P2P3P4
A7604
B4013
C3011

再用更新的 Work 向量和 Need 矩阵重复步骤(2),最后得到一个安全序列 {P1, P3, P4, P2, P0} 。

3)银行家算法描述

【举例】:假设当前系统中资源的分配和剩余情况如下表所示。

最大需求矩阵 Max :定义系统中 5 个进程中的每个进程对 3 类资源的最大需求。

MaxP0P1P2P3P4
A73924
B52023
C32223

分配矩阵 Allocation:定义系统中每类资源当前已分配给每个进程的资源数。

AllocationP0P1P2P3P4
A02320
B10010
C00212

可利用资源向量 Available:含有 3 个元素的数组,其中每个元素代表一类可用的资源数目。

ABC
332

在执行安全性算法开始时,工作向量 Work(表示系统中的剩余可用资源数目)= Available = (3, 3, 2) 。

需求矩阵 Need:表示每个进程接下来最多还需要多少资源。

NeedP0P1P2P3P4
A71604
B42013
C32011

(a)P1 请求资源:P1 发出请求向量 Request1(1, 0, 2) ,系统按银行家算法进行检查:

  • Request1(1, 0, 2) < Need1(1, 2, 2)
  • Request1(1 , 0, 2) < Available(3, 3, 2)

系统先假定可为 P1 分配资源,并修改:

  • Available = Available - Request1 = (2, 3, 0)
  • Allocation1 = Allocation1 + Request1 = (3, 0, 2)
  • Need1 = Need1 - Request1 = (0, 2, 0)

令 Work = Available = (2, 3, 0) ,再利用安全性算法检查此时系统是否安全。由所进行的安全性检查得知,可找到一个安全序列 {P1, P3, P4, P0, P2} 。因此,系统是安全的,可以立即将 P1 所申请的资源分配给它。

(b)P4 请求资源:P4 发出请求向量 Reques4(3, 3, 0) ,系统按银行家算法进行检查:

  • Reques4(3, 3, 0) < Need4(4, 3, 1)
  • Reques4(3, 3, 0) > Available(2, 3, 0) ,让 P4 等待。

(c)P0 请求资源:P0 发出请求向量 Requst0(0, 2, 0) ,系统按银行家算法进行检查:

  • Request0(0, 2, 0) < Need0(7, 4, 3)
  • Request0(0, 2, 0) < Available(2, 3, 0)

系统暂时先假定可为 P0 分配资源,并修改有关数据:

  • Available = Available - Request0 = (2, 1, 0)
  • Allocation0 = Allocation0 + Request0 = (0, 3, 0)
  • Need0 = Need0 - Request0 = (7, 2, 3)

进行安全性检查:可用资源 Available(2, 1, 0) 已不能满足任何进程的需要,因此系统进入不安全状态,因此拒绝 P0 的请求,让 P0 等待,并将 Available, Allocation0, Need0 恢复为之前的值。

4)总结

(a)数据结构:

  • 一维数组 Available(长度为 m)表示:还有多少可用资源;
  • Max 矩阵(n×m)表示:各进程对资源的最大需求数;
  • Allocation 矩阵(n×m)表示:已经给各进程分配了多少资源;
  • Need 矩阵(= Max - Allocation)表示:各进程最多还需要多少资源;
  • 一维数组 Request(长度为 m)表示:进程此次申请的各种资源数。

(b)安全性算法步骤:

  • 检查当前的剩余可用资源是否能满足某个进程的最大需求,如果可以,就把该进程加入安全序列,并把该进程持有的资源全部回收。
  • 不断重复上述过程,看最终是否能让所有进程都加入安全序列。

(c)银行家算法步骤:

  • ① 检查此次申请是否超过了之前声明的最大需求数;
  • ② 检查此时系统剩余的可用资源是否还能满足这次请求;
  • ③ 试探着分配,更改各数据结构;
  • ④ 用安全性算法检查此次分配是否会导致系统进入不安全状态。

主要注意:系统处于不安全状态未必死锁,但死锁时一定处于不安全状态。系统处于安全状态一定不会死锁。

四、死锁检测和解除

【总结】:

前面介绍的死锁预防和避免算法,都是在为进程分配资源时施加限制条件或进行检测,如果系统中既不采取预防死锁的措施,也不采取避免死锁的措施,系统就很可能发生死锁。在这种情况下,系统应当提供两个算法:

  • ① 死锁检测算法:用于检测系统状态,以确定系统中是否发生了死锁。

  • ② 死锁解除算法:当认定系统中已经发生了死锁,利用该算法可将系统从死锁状态中解脱出来。

1. 资源分配图

系统死锁可利用资源分配图来描述。如下图所示,用圆圈代表一个进程,用框代表一类资源。由于一种类型的资源可能有多个,因此用框中的一个圆代表一类资源中的一个资源。从进程到资源的有向边称为请求边,表示该进程申请一个单位的该类资源;从资源到进程的边称为分配边,表示该类资源已有一个资源分配给了该进程。

在上图所示的资源分配图中,进程 P1 已经分得了两个 R1 资源,并又请求一个R2 资源;进程 P2 分得了一个 R1 资源和一个 R2 资源,并又请求一个 R1 资源。

2. 死锁定理

简化资源分配图可检测系统状态 S 是否为死锁状态。检测死锁的算法如下:

3. 死锁解除

五、小结

1、为什么会产生死锁?产生死锁有什么条件?

由于系统中存在一些不可剥夺资源,当两个或两个以上的进程占有自身的资源并请求对方的资源时,会导致每个进程都无法向前推进,这就是死锁。死锁产生的必要条件有 4 个,分别是互斥条件、不剥夺条件、请求并保待条件和循环等待条件。

  • 互斥条件是指进程要求分配的资源是排他性的,即最多只能同时供一个进程使用。
  • 不剥夺条件是指进程在使用完资源之前,资源不能被强制夺走。
  • 请求并保持条件是指进程占有自身本来拥有的资源并要求其他资源。
  • 循环等待条件是指存在一种进程资源的循环等待链。

2、有什么办法可以解决死锁问题?

死锁的处理策略可以分为预防死锁、避免死锁及死锁的检测与解除。

  • 死锁预防是指通过设立一些限制条件,破坏死锁的一些必要条件,让死锁无法发生。
  • 死锁避免指在动态分配资源的过程中,用一些算法防止系统进入不安全状态,从而避免死锁。
  • 死锁的检测和解除是指在死锁产生前不采取任何措施,只检测当前系统有没有发生死锁,若有,则采取一些措施解除死锁。

3、死锁与饥饿

一组进程处于死锁状态是指组内的每个进程都在等待一个事件,而该事件只可能由组内的另一个进程产生。这里所关心的主要是事件是资源的获取和释放。与死锁相关的另一个问题是无限期阻塞(Indefinite Blocking)或饥饿(Starvation),即进程在信号量内无穷等待的情况。

产生饥饿的主要原因是:在一个动态系统中,对于每类系统资源,操作系统需要确定一个分配策略,当多个进程同时申请某类资源时,由分配策略确定资源分配给进程的次序。有时资源分配策略可能是不公平的,即不能保证等待时间上界的存在。在这种情况下,即使系统没有发生死锁,某些进程也可能会长时间等待。当等待时间给进程推进和响应带来明显影响时,称发生了进程 “饥饿” ,当 “饥饿” 到一定程度的进程所赋予的任务即使完成也不再具有实际意义时,称该进程被 “饿死” 。例如,当有多个进程需要打印文件时,若系统分配打印机的策略是最短文件优先,则长文件的打印任务将由于短文件的源源不断到来而被无限期推迟,导致最终 “饥饿” 甚至 “饿死” 。”饥饿” 并不表示系统一定会死锁,但至少有一个进程的执行被无限期推迟。

“饥饿” 与死锁的主要差别如下:

  • 进入 “饥饿” 状态的进程可以只有一个,而因循环等待条件而进入死锁状态的进程却必须大于或等于两个。

  • 发生 “饥饿” 的进程的状态可能是就绪态(长期得不到处理机),也可能是阻塞态(如长期得不到所需的 I/O 设备),而发生死锁的进程的状态则必定是阻塞态。

4、银行家算法的工作原理

银行家算法的主要思想是避免系统进入不安全状态。在每次进行资源分配时,它首先检查系统是否有足够的资源满足要求,若有则先进行试分配,并对分配后的新状态进行安全性检查。若新状态安全,则正式分配上述资源,否则拒绝分配上述资源。这样,它保证系统始终处于安全状态,从而避免了死锁现象的发生。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Kusunoki_D

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

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

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

打赏作者

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

抵扣说明:

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

余额充值