死锁、活锁和饥饿-架构快速进阶教程
1. 简介
在多进程并发环境中,多个进程可能会争用一组有限的资源。如果进程请求资源,而该资源当前不可用,则进程将等待该资源。有时,此等待过程永远不会成功访问资源。这种对资源的等待会导致三种情况——死锁、活锁和饥饿。
在本教程中,我们将讨论这三个条件。
2. 死锁
在本节中,我们将首先讨论死锁、其必要条件以及如何防止死锁。
2.1. 什么是死锁?
死锁是指进程由于资源获取而相互阻塞的情况,并且没有一个进程在等待另一个进程持有的资源时取得任何进展
上图显示了进程 1 和进程 2 之间的死锁场景。两个进程都持有一个资源,并等待另一个进程持有的另一个资源。这是一个死锁情况,因为进程 1 或进程 2 都无法取得进展,直到其中一个进程放弃其资源。
2.2. 死锁的必要条件
若要成功地将方案描述为死锁,必须同时满足以下四个条件:
互斥:进程需要以不可共享模式至少保存一个资源。请求该资源的任何其他进程都需要等待。
保留和等待:进程必须保留一个资源,并请求当前由其他进程保留的其他资源。
无抢占:无法从进程中强制释放资源。进程只有在认为释放时才能自愿释放资源。
循环等待:进程{p0, p1, p2,.., pn}的存在方式是p0等待p1拥有的资源,pn-1等待p0拥有的资源。
2.3. 如何防止死锁
为了防止死锁的发生,上一节中讨论的必要条件中至少有一个不应成立。让我们检查一下这些条件中的任何一个为假的可能性:
互斥:在某些情况下,此条件可能是假的。例如,在只读文件系统中,可以向一个或多个进程授予可共享访问权限。但是,此条件不能总是错误的。原因是某些资源本质上是不可共享的。例如,互斥锁是不可共享的资源。
等待:为了确保永远不会发生保留和等待条件,我们需要保证一旦进程请求资源,它当时不会持有任何其他资源。通常,进程应在开始执行之前请求所有资源。
无抢占:若要使此条件为 false,进程需要确保在新请求的资源不可用时自动释放所有当前持有的资源。
循环等待:通过对所有资源类型施加总顺序,并确保每个进程以递增的枚举顺序请求资源,可以使该条件为假。因此,如果有一组n个资源{r1,r2,..Rn},一个进程需要资源r1和r2来完成一个任务,它需要先请求r1,再请求r2。
3. 活锁
在本节中,我们将讨论实时锁,它类似于死锁,但有细微的区别。
3.1. 什么是活锁?
在活锁的情况下,活锁场景中涉及的进程的状态不断变化。另一方面,这些过程仍然相互依赖,永远无法完成其任务。
上图显示了活锁的示例。“进程 1”和“进程 2”都需要一个共同的资源。每个进程检查另一个进程是否处于活动状态。如果是这样,则它将资源移交给另一个进程。但是,由于两者都处于非活动状态,两者都无限期地将资源移交给对方。
一个真实世界的活锁示例发生在两个人互相打电话并且都发现线路繁忙时。两位先生都决定挂断电话,并尝试在相同的时间间隔后打电话。因此,在下一次重试中,他们最终也处于相同的情况。这是实时锁的一个例子,因为它可以永远持续下去。
3.2. 死锁和活锁的区别?
尽管性质相似,但死锁和活动锁并不相同。在死锁中,死锁中涉及的进程将无限期地停滞,并且不会进行任何状态更改。但是,在实时锁定方案中,进程会相互阻塞并无限期等待,但它们会不断更改其资源状态。值得注意的是,资源状态更改没有任何影响,并且不会帮助进程在其任务中取得任何进展。
4. 饥饿
在本节中,我们将讨论饥饿,它通常是由于死锁、活锁或贪婪过程引起的。
4.1. 什么是饥饿?
饥饿是一个过程的结果,该过程无法定期访问完成任务所需的共享资源,因此无法取得任何进展。
上图显示了 CPU 的“进程 2”和“进程 3”匮乏的示例,因为“进程 1”长时间占用它。
4.2. 什么原因导致饥饿?
饥饿可能是由于死锁、活锁或由另一个进程引起的。
正如我们所看到的,在死锁或实时锁的情况下,进程与另一个进程竞争以获取所需的资源以完成其任务。但是,由于死锁或活锁方案,它无法获取资源,并且通常缺乏资源。
此外,当其他进程等待同一资源时,可能会发生一个进程重复获得对共享资源的访问权限或将其使用更长时间的情况。因此,等待进程被贪婪进程匮乏资源。
4.3. 避免饥饿
防止匮乏的可能解决方案之一是使用具有优先级队列的资源调度算法,该算法也使用老化技术。老化是一种定期增加等待过程优先级的技术。使用此方法,任何等待资源持续时间较长的进程最终都会获得更高的优先级。由于资源共享是通过流程的优先级驱动的,因此没有流程会无限期地缺乏资源。
防止饥饿的另一种解决方案是在将资源分配给进程时遵循轮循机制模式。在此模式中,资源公平地分配给每个进程,从而在再次将其分配给另一个进程之前提供使用该资源的机会。