死锁:它是什么,如何检测、处理和预防-架构快速进阶教程
1. 概述
在本教程中,我们将通过实际示例探讨如何预防、避免、检测和忽略死锁。
2. 死锁简介
在进程共享资源的几乎任何情况下都可能发生死锁。它可以发生在任何计算环境中,但它在分布式系统中很普遍,其中多个进程在不同的资源上运行。
在此情况下,一个进程可能正在等待另一个进程已持有的资源。死锁类似于先有鸡还是先有蛋的问题。
让我们看一个死锁的例子:
假设有三个进程 P1、P2 和 P3,以及三个资源 R1、R2 和 R3。
现在,假设 P1 请求由 P2 持有的资源 R2。在这种情况下,P1 不会在没有 R2 的情况下继续,并且会无限期等待,因为 P2 无法释放资源 2,直到它获得 P3 持有的 R3。P1 和 P3 也是如此。因此,这是操作系统中死锁情况的理想示例。
3. 死锁的必要条件
如果以下四个条件同时成立,则可能会出现死锁:
第一个条件是互斥。在这种情况下,我们不能同时在不同进程之间共享资源。
例如,如果两个人想同时打印一张纸,则此过程无法完成。必须等到系统释放打印(资源)。因此,我们一次只能将资源分配给一个进程。
死锁的第二个必要条件是保留和等待或资源保留。在这种情况下,进程同时保存至少一个资源,并一次等待另一个资源。在这里,进程未处于运行状态。它处于等待状态。
第三个条件是无抢占。如果进程保存资源,则在释放资源之前,不能从进程中强行带走资源。当进程处于等待状态时,此语句也成立。
死锁的最后一个条件是循环等待。假设进程 P1 正在等待资源 R2。现在,特定资源 R2 已由进程 P2 持有。进程 P2 正在等待由下一个进程持有的资源。这将持续到最后一个进程正在等待第一个进程持有的资源。
循环等待条件创建一个循环链,并将所有进程置于等待状态。
4. 死锁预防
在死锁预防过程中,操作系统将通过避免导致死锁的四种情况中的任何一种来防止死锁的发生。如果操作系统可以避免任何必要条件,则不会发生死锁。
4.1. 不相互排斥
这意味着多个进程可以同时访问单个资源。这是不可能的,因为如果多个进程同时访问同一资源,就会出现混乱。此外,不会完成任何过程。所以这是不可行的。因此,操作系统无法避免相互排斥。
让我们举一个实际的例子来理解这个问题。杰克和琼斯分享一碗汤。他们俩都想喝同一个碗里的汤,同时用一把勺子,这是不可行的。
4.2. 无保留和等待
为了避免保留和等待,在开始执行之前,有许多方法可以获取所有必需的资源。但这也是不可行的,因为一个进程一次将使用一个资源。在这里,资源利用率将非常低。
在开始执行之前,该过程不知道完成它需要多少资源。除此之外,进程将完成并释放资源的总线时间也是未知的。
另一种方法是,如果进程持有资源并希望拥有其他资源,那么它必须释放获取的资源。这样,我们可以避免等待和等待条件,但它可能会导致饥饿。
4.3. 删除无抢占权
导致死锁的原因之一是没有抢占。这意味着 CPU 无法强行从任何进程中获取获取的资源,即使该进程处于等待状态也是如此。如果我们能够消除无抢占并从等待进程中强行获取资源,我们就可以避免死锁。这是避免死锁的可实现逻辑。
例如,这就像从琼斯手中拿过碗,当杰克来喝汤时把它交给他。让我们假设琼斯先来并获得了资源并进入等待状态。现在杰克来了,餐饮服务员用力从琼斯手中接过碗,告诉他如果你处于等待状态,就不要拿碗。
4.4. 删除循环等待
在循环等待中,两个进程停滞在等待彼此持有的资源状态。为了避免循环等待,我们为所有资源分配一个数字整数值,并且进程必须按递增或递减顺序访问资源。
如果进程按递增顺序获取资源,则仅当该资源具有更高的整数值时,它才能访问新的附加资源。如果该资源的整数值较小,则必须在获取新资源之前释放获取的资源,反之亦然,以便降序。
例如,餐饮服务商为碗和勺子分配了一个整数值,分别为 1 和 2,以便可以按递增顺序访问资源。现在,如果琼斯有一个碗,那么他就可以有勺子。但是由于杰克有一个整数值更大的勺子,他必须先离开勺子,然后再拿碗。之后,他会拿勺子喝汤。
5. 避免死锁
避免死锁的方法有助于操作系统避免死锁的发生。在开始执行之前,操作系统将维护整个生命周期中进程所需的最大资源的日志。操作系统将在将任何新请求的资源分配给任何进程之前不断检查系统的状态。
基本上,在避免死锁的情况下,操作系统将尽量不处于循环等待状态。如果操作系统可以将所有请求的资源分配给进程,而不会在将来导致死锁,则称为安全状态。如果操作系统无法分配所有请求的资源而不会在将来导致死锁,则称为不安全状态。
5.1. 资源分配图(RAG)算法
使用 RAG,可以预测操作系统中死锁的发生。
资源分配图是所有已分配资源、可用资源和操作系统当前状态的图形视图。我们可以计算出为每个流程分配了多少资源以及将来需要多少资源。因此,我们可以轻松避免僵局。
由于每个图形都有顶点和边,因此RAG也有顶点和边。RAG 有两个顶点:流程顶点和资源顶点,以及两个边:分配边和请求边。大多数情况下,我们用矩形表示顶点,用圆形表示边缘:
RAG 算法有一个限制,即它只有在所有资源都有一个实例时才有效。如果资源有多个实例,则在确定死锁时不确定。会有形成圆圈的可能,但可能会导致僵局。因此,在这种情况下,我们使用银行家的算法来确定死锁。
例如,让两个进程 P1、P2 和两个资源 R1、R2:
![](https://img2023.cnblogs.com/blog/14085/202302/14085-20230215104250827-2082152326.png)
在这里,P1 使用 R1,将来将使用 R2,表示为虚线。目前,它是免费的。P2 正在请求 R2,但操作系统不会分配它,因为稍后 P1 将需要它。所以它会避免形成一个圆圈。因此,它避免了死锁发生的可能性。但从上述解决方案来看,R2 将不会使用。因此,资源利用率会降低。
5.2. 银行家算法
当资源有多个实例时,我们使用银行家的算法。它被称为银行家算法,因为银行遵循它以这种方式管理资源,这样他们就不会耗尽资源并避免进入不安全状态。银行在分配或批准银行系统中的任何贷款之前使用此算法。
在这个算法中,我们需要一些预定的数据,比如每个进程完成生命周期所需的最大资源和操作系统中的总可用资源。从这些数据中,它将形成一个安全的序列。如果操作系统按该顺序运行所有进程,它将避免死锁的发生。
此算法维护总可用资源、每个进程所需的最大资源、分配给每个进程的总资源以及当前所需资源的矩阵。
6. 死锁检测和避免
在此方法中,OS 假定将来会发生死锁。因此,它以一定的时间间隔运行死锁检测机制,当它检测到死锁时,它会启动恢复方法。
操作系统的主要任务是检测死锁。我们之前已经介绍了两种检测方法。
在这里,我们使用相同的方法进行一些即兴创作:
在等待图方法中,操作系统检查圆的形成。它在某种程度上与资源分配图(RAG)相同,但存在一些差异。大多数情况下,它会导致 RAG 和等待图之间的混淆。
RAG 和等待图之间的主要区别在于每个图包含的顶点数。RAG 图有两个顶点:资源和进程。等待图有一个顶点:进程。
我们还可以使用 RAG 创建一个等待图:
等待图不会形成一个圆圈,这意味着它不会导致系统陷入死锁。
对于多实例资源,我们使用安全算法,该算法使用与银行家算法相同的方法。但它没有所需的最大资源矩阵。它只有三个矩阵:已分配、可用和当前需求矩阵。
现在,一旦操作系统检测到死锁,它就会启动恢复方法。从死锁中恢复有两种主要方法:
6.1. 乐观主义方法
乐观主义方法的一个例子是资源和流程抢占。在这种方法中,操作系统将选择一些进程并抢占其资源,然后将它们分配给其他进程。因此,操作系统将尝试打破循环。
这种方法的一个缺点是,同一进程有可能成为抢占的受害者。在这种情况下,该过程将陷入饥饿状态。
另一种方法,其中操作系统将回滚到未发生死锁的某个安全状态。但为此,操作系统必须维护一些处于安全状态的日志。
此方法的缺点之一是没有决策参数来选择进程回滚的顺序。
6.2. 悲观主义方法
一种悲观主义的方法是中止所有陷入僵局的进程。这是打破循环以从死锁中恢复的最简单方法,但它也是处理死锁的最昂贵方法。在这种方法中,我们杀死所有进程,操作系统将根据需要丢弃它们或稍后重新启动部分进程。
此外,我们可以一次中止一个进程,直到消除系统中的死锁。
在此方法中,操作系统一次杀死一个进程,并选择完成最少工作的进程。之后,它运行死锁检测算法以验证死锁是否已恢复。如果不恢复,那么它将继续终止进程,直到消除死锁。
7. 死锁无知
这是处理死锁的广泛使用的方法之一。在此方法中,OS 假定永远不会发生死锁。如果出现死锁情况,操作系统将重新启动系统。这种方法在操作系统适用于最终用户的地方非常流行。
死锁忽略方法用于Linux和基于Windows的操作系统,其中用户直接与系统联系。
8. 结论
在本教程中,我们彻底讨论了操作系统中死锁的概念。我们举了一个例子来说明操作系统中死锁背后的想法。
此外,我们还探索了防止、避免、检测和忽略死锁的各种方法。