进程因申请资源而等待而等待状态无法结束的现象称为死锁。本章主要讨论死锁的产生条件与解决方法。
系统模型
为了描述进程与资源的关系,需要建立模型。资源被分为若干中,种类是相同的,因此我们只需要考虑每种资源有多少个。
进程首先要申请资源,成功后资源分配给该进程,进程使用资源,完成后释放资源。
资源分配图中,用圆圈来表示进程,用方框表示一种类型的资源,其中的每个黑点表示一个资源实例。由进程指向资源的边为申请边,由资源指向进程的边为分配边。
死锁的必要条件
死锁有四个必要条件:
- 互斥:同一资源实例不能被多个进程同时共享。
- 非抢占:进程不可以抢占另一个进程正在使用的资源。
- 占有等待:所有参与死锁构成的进程中,每个进程必须至少占有一个资源,且至少等待一个被其他死锁进程占有的资源。
- 循环等待:上述占有等待的关系构成有向环路。
表现在资源分配图上,有环是死锁的必要不充分条件。
死锁的处理方法
忽略死锁确实是一种常用的策略,但这里我们考虑真正的死锁处理方法:预防死锁、避免死锁、检测并恢复死锁。
死锁预防
预防死锁指确保四个必要条件中至少有一个不成立,其通过限制资源的申请来实现。
破坏互斥条件通常是不可行的。
破坏占有等待的常见策略是确保等待时不占有。具体的方案包括等待时释放资源,或者只有在不拥有资源时才能申请资源且必须一口气申请完所有资源。这些方法会大幅度降低资源利用率且会导致饥饿现象。
允许抢占,即进程在等待时,其已经拥有的资源可以被抢占。这种方法只适用于那些状态可以轻易保存和恢复的资源。
破除循环,通常要求请求必须按照某种全序来逐个进行。
死锁避免
死锁避免算法通过动态检查资源分配状态,确保循环等待条件不可能程理。它分析各进程以后如何申请资源的信息,考虑现有可用资源、每个进程已经分配和还需要分配的资源的数目的关系。
如果按照某个顺序为每个进程分配资源,等待该进程完成后再继续,如此操作就可以避免死锁,那么这个顺序称为安全序列。存在安全序列的系统状态称为安全状态。安全状态一定不会发生死锁。
死锁避免算法的思想是始终保持系统处于安全状态中。
资源分配图算法
资源分配图算法用于处理每种资源类型只有一个资源实例的情况。
在前述资源分配图的基础上,增加一类边称为需求边,由进程指向某个资源,表示在将来的某个时候该进程要申请这种资源。当申请发生时,需求边变成申请边。当资源释放时,申请边变成需求边。
资源分配图算法只有在申请边变成分配边不会导致出现由分配边时才允许申请。
银行家算法
银行家算法的思想是,在银行放贷的过程中,如果当前已经不能满足一个客户的全部需要时,我们不给他再分配更多。具体而言,我们每次分配,都保证系统处在安全状态,在满足这一条件下,能分就分,不再考虑更多。
银行家算法需要维护一些数据结构。假设有 (n) 个进程,(m) 种资源,(a[i]) 表示第 (i) 种资源现有实例的数量,(M[i][j]) 表示进程 (i) 对第 (j) 种资源的最大需求,(A[i][j]) 表示已经分配给进程 (i) 多少个资源 (j),(N[i][j]) 表示进程 (i) 还需要多少个资源 (j)。这里显然有 (A+N=M)。以下我们用 (X_i) 表示矩阵 (X) 的 (i) 行向量。
银行家算法由安全性算法和资源请求算法两部分构成。
安全性算法:
work[]=a;
finish[]=0;
While true:
Find i s.t. finish[i]=0, Ni<=work;
If i: work+=Ai, finish[i]=1;
Else Return finish[]=1;
资源请求算法:
// 假设限制 Pi 发出了请求 Ri
If Ri>Ni: Error();
If Ri>a: Wait();
// 尝试分配
a-=Ri;
Ai+=Ri;
Ni-=Ri;
If IsSafe()==false: a+=Ri, Ai-=Ri, Ni+=Ri, Wait();
死锁检测
资源都只有单个实例的情况下,直接用等待图即可。
资源具有多个实例的情况下,类似银行家算法,维护数据结构 a,A,R(各进程当前的资源请求情况),利用死锁检测算法来判断系统是否处于死锁状态。
死锁检测算法
work[]=a;
finish[]=0;
While true:
Find i s.t. finish[i]=0, Ri<=work;
If i: work+=Ai, finish[i]=1;
Else Return finish[]=1;
注意死锁检测算法和安全性算法的唯一区别是找 i 时判断条件中,安全性算法是 Ni<=work
而死锁检测算法是 Ri<=work
。从这里我们可以看出安全性算法的要求比死锁检测算法要高。安全性算法考虑了未来,而死锁检测算法只关心当下。
由于死锁检测算法的复杂度较高,一般执行频率较低,或只在系统状态满足特定条件时执行。
死锁恢复
死锁恢复的自动解决方案有两类。
终止进程,可以终止所有死锁进程,也可以从代价最小的进程开始,贪心地一个个终止直到死锁解除。
资源抢占,需要选择一个牺牲品,将它的状态回滚到一个安全的位置。为了防止饥饿,可以在牺牲品选择的代价中引入与回滚次数相关的因子。