死锁是一组互相竞争系统资源或进行通信的进程间的永久阻塞。当一组进程中的每一个进程都在等待某个事件,而仅有这组进程中被阻塞的其他进程才可触发该事件时,就称这组进程发生了死锁。死锁在没有外界干预的情况下是永久性的。
死锁有3个必要条件:
除此之外,要产生死锁,最关键的条件是循环等待:
第4个条件是前3个条件的潜在结果,之所以产生循环等待是因为有前三个条件。
解决死锁的方式有3种:
死锁预防策略是试图设计一种方式来排除发生死锁的可能性,预防策略分为两类:
互斥条件一般不可能禁止,某些资源必须同一时间只能由一个进程操作才能保证其安全性。
为了预防占有且等待条件条件,可以要求进程一次性请求所有资源,如果无法一次性请求那么就进行等待。但是这样做存在的问题有:
预防不可抢占的策略有以下几种:
循环等待的预防可以通过定义资源获取的访问顺序。若定义资源获取序列 { A , B , C , D } \{A,B,C,D\} {A,B,C,D},当获取到资源 B B B 时,只能够尝试获取资源 C C C 或者 D D D。当然这个预防方式可能是低效的,可能会在没有必要的情况下拒绝资源的获取。
解决死锁问题的另一种方法是死锁避免,它和死锁预防的差别很小,可以把它理解为死锁预防的一种特例。
死锁避免策略在允许三个必要条件存在的条件下,来确保永远不会达到死锁点。
死锁避免方法有:
相比死锁预防策略,死锁避免策略并发性更强。但是在使用中也有诸多限制:
我们先考虑第一个死锁避免方式,考虑一个有着 n n n 个进程和 m m m 种不同类型资源的系统,定义以下向量和矩阵。
从中可以看出以下关系成立:
所以死锁避免的策略为:若一个新的进程资源需求会导致死锁,则拒绝启动这个新进程,仅当
R j ≥ C j ( n + 1 ) + ∑ n i = 1 C i j , j ∈ [ 0 , m ] R_j\geq C_{j(n+1)}+\sum_n^{i=1}C_{ij},j \in [0,m] Rj≥Cj(n+1)+n∑i=1Cij,j∈[0,m]
时才启动一个新进程 P n + 1 P_{n+1} Pn+1。当然这个策略不是最优的,它只假设了最坏的情况,即所有进程同时发出它们的最大请求。
第二个死锁避免方式又称银行家算法,需要定义安全状态和不安全状态,安全状态指至少一个资源分配序列不会导致死锁,即所有进程都能够顺利运行到结束,不安全状态指非安全的一个状态。
为了准确描述银行家算法,我们给出一个例子:
一共有4个进程和3种资源,资源总量的向量 R = ( 9 , 3 , 6 ) R=(9,3,6) R=(9,3,6),每个进程对每种资源的最大需求矩阵 C = [ 3 2 2 6 1 3 3 1 4 4 2 2 ] C=\left[ \begin{matrix} 3 & 2 & 2 \\ 6 & 1 & 3 \\ 3 & 1 & 4 \\ 4 & 2 & 2 \\ \end{matrix} \right] C=⎣⎢⎢⎡363421122342⎦⎥⎥⎤,假设当前资源分配矩阵 A A A 为: A = [ 1 0 0 6 1 2 2 1 1 0 0 2 ] A=\left[ \begin{matrix} 1 & 0 & 0 \\ 6 & 1 & 2 \\ 2 & 1 & 1 \\ 0 & 0 & 2 \\ \end{matrix} \right] A=⎣⎢⎢⎡162001100212⎦⎥⎥⎤,问当前状态是否为安全状态?或者说,可用资源能否满足当前的分配情况和任何一个进程的最大需求?
从上述条件可知,发现剩余资源向量为 V = ( 0 , 1 , 1 ) V=(0,1,1) V=(0,1,1)。
观察进程 P 1 P_1 P1,发现 P 1 P_1 P1是不可能在此时正常结束的,因为进程 P 1 P_1 P1还需要2个 R 1 R_1 R1资源、两个 R 2 R_2 R2资源和两个 R 3 R_3 R3资源。
观察进程 P 2 P_2 P2,发现进程只需要一个 R 3 R_3 R3资源就达到了所需的最大资源,从而可以顺利运行完成。当 P 2 P_2 P2进程执行结束后,资源便会归还给资源池。
P 2 P_2 P2进程在获得到一个 R 3 R_3 R3资源并运行结束后,此时的资源分配矩阵 A = [ 1 0 0 0 0 0 2 1 1 0 0 2 ] A=\left[ \begin{matrix} 1 & 0 & 0 \\ 0 & 0 & 0 \\ 2 & 1 & 1 \\ 0 & 0 & 2 \\ \end{matrix} \right] A=⎣⎢⎢⎡102000100012⎦⎥⎥⎤,可用资源向量 V = ( 6 , 2 , 3 ) V=(6,2,3) V=(6,2,3)。
现在再来看进程 P 1 P_1 P1,发现进程 P 1 P_1 P1可以正常完成,假设现在选择 P 1 P_1 P1进程执行并运行结束后,资源分配矩阵 A = [ 0 0 0 0 0 0 2 1 1 0 0 2 ] A=\left[ \begin{matrix} 0 & 0 & 0 \\ 0 & 0 & 0 \\ 2 & 1 & 1 \\ 0 & 0 & 2 \\ \end{matrix} \right] A=⎣⎢⎢⎡002000100012⎦⎥⎥⎤
现在还差进程 P 3 P_3 P3和进程 P 4 P_4 P4等待执行,此时剩余资源向量 V = ( 7 , 2 , 3 ) V=(7,2,3) V=(7,2,3),下一步也可以正常将全部资源分配给 P 3 P_3 P3并执行完成,最后再分配给 P 4 P_4 P4进程。
至此4个进程全部执行完成,执行顺序为 P 2 → P 1 → P 3 → P 4 P_2→P_1→P_3→P_4 P2→P1→P3→P4。
总的来说,银行家算法就是当进程请求一组资源时,先判断这个进程在请求了指定的资源后能不能处于安全状态,如果可以,就同意这个请求,如果不行,则阻塞该进程直到能够满足。
再举个例子,同样是4个进程3种资源,资源总量和需求矩阵和上述例子相同,假设初始资源分配矩阵 A = [ 1 0 0 5 1 1 2 1 1 0 0 2 ] A=\left[ \begin{matrix} 1 & 0 & 0 \\ 5 & 1 & 1 \\ 2 & 1 & 1 \\ 0 & 0 & 2 \\ \end{matrix} \right] A=⎣⎢⎢⎡152001100112⎦⎥⎥⎤。
可以计算出剩余资源向量 V = ( 1 , 1 , 2 ) V=(1,1,2) V=(1,1,2),现在考虑进程 P 1 P_1 P1,假设 P 1 P_1 P1请求一个 R 1 R_1 R1资源和一个 R 3 R_3 R3资源,如果同意了,那么资源分配矩阵 A = [ 2 0 1 5 1 1 2 1 1 0 0 2 ] A=\left[ \begin{matrix} 2 & 0 & 1 \\ 5 & 1 & 1 \\ 2 & 1 & 1 \\ 0 & 0 & 2 \\ \end{matrix} \right] A=⎣⎢⎢⎡252001101112⎦⎥⎥⎤, V = ( 0 , 1 , 1 ) , V=(0,1,1), V=(0,1,1),此时可以得到每个进程需要的资源矩阵: C − A = [ 1 2 1 1 0 2 1 0 3 4 2 0 ] C-A=\left[ \begin{matrix} 1 & 2 & 1 \\ 1 & 0 & 2 \\ 1 & 0 & 3 \\ 4 & 2 & 0 \\ \end{matrix} \right] C−A=⎣⎢⎢⎡111420021230⎦⎥⎥⎤
这是个不安全的状态,因为每个进程都需要至少一个 R 1 R_1 R1资源,因此进程 P 1 P_1 P1的请求将会被拒绝,因为如果请求了有可能会导致死锁。
银行家算法不能够准确预测死锁,它的策略是将死锁的可能性降到0。
死锁检测不会限制资源访问或者约束进程的行为,当发生死锁时,通过死锁的检测来解除死锁状态。
死锁检测的一个常用算法描述如下:
使用上述定义的矩阵 C C C和矩阵 A A A,然后定义一个请求矩阵 Q Q Q, Q i j Q_{ij} Qij表示进程 i i i正在请求的 j j j类资源的数量。执行以下步骤:
当上述算法执行结束后,每个未被标记的进程都存在死锁。该算法不能预防死锁,它只能确定是否存在死锁。
现在定义资源需求矩阵为 Q = [ 0 1 0 0 1 0 0 1 0 1 0 0 0 0 1 1 0 1 0 1 ] Q=\left[ \begin{matrix} 0 & 1 & 0 & 0 & 1\\ 0 & 0 & 1 & 0 & 1\\ 0 & 0 & 0 & 0 & 1\\ 1 & 0 & 1 & 0 & 1\\ \end{matrix} \right] Q=⎣⎢⎢⎡00011000010100001111⎦⎥⎥⎤,资源总量 R = ( 2 , 1 , 1 , 2 , 1 ) R=(2,1,1,2,1) R=(2,1,1,2,1),此时:资源分配矩阵 A = [ 1 0 1 1 0 1 1 0 0 0 0 0 0 1 0 0 0 0 0 0 ] A=\left[ \begin{matrix} 1 & 0 & 1 & 1 & 0\\ 1 & 1 & 0 & 0 & 0\\ 0 & 0 & 0 & 1 & 0\\ 0 & 0 & 0 & 0 & 0\\ \end{matrix} \right] A=⎣⎢⎢⎡11000100100010100000⎦⎥⎥⎤, V = ( 0 , 0 , 0 , 0 , 1 ) V=(0,0,0,0,1) V=(0,0,0,0,1),问现在是否存在死锁?
按照算法执行步骤:
参考资料:《操作系统精髓与设计》