死锁处理:预防、避免、检测
基于通用操作系统概念 · 核于 2026-08
速查
- 四种策略(按严格度递减):预防 > 避免 > 检测恢复 > 鸵鸟(忽略)。越严格越不死锁,但代价越大。
- 预防(Prevention):破坏四必要条件之一,保证永不死锁。常用:资源有序分配(破坏循环等待)、一次性申请全部资源(破坏占有等待)。代价:资源利用率与并发度下降。
- 避免(Avoidance):不破坏四条件,但每次分配前先算系统是否仍处于安全状态——是则分配,否则让进程等待。代表:银行家算法(Banker's Algorithm)。代价:需事先知道每个进程的最大需求,每次分配都要运算。
- 安全状态:存在一个安全序列(进程能依次完成、每步都能满足请求的顺序)。处于安全状态一定不会死锁;不安全状态可能死锁(但不一定,只是有风险)。
- 银行家算法:维护 Available(可用)、Max(最大需求)、Allocation(已分配)、Need(=Max−Allocation)。每次请求先试探性分配,再跑安全性算法判断是否仍安全,安全才真分配,否则回滚让进程等待。
- 检测与恢复(Detection & Recovery):允许死锁发生,周期性运行死锁检测算法(类似银行家,但用当前实际 Allocation 而非 Need 上限);检测到死锁后恢复:终止进程(全杀/逐个杀)、抢占资源(选牺牲者、回滚)。
- 鸵鸟策略(Ostrich Algorithm):忽略死锁,假装不会发生。Linux/Windows 等通用 OS 的实际选择——死锁概率低、预防代价高,权衡后选择忽略,由开发者自己避免(锁排序、超时)。
- 应用层实践:超时(
tryLock(timeout))、锁排序(所有线程按相同顺序加锁)、避免嵌套锁、用更高层抽象(消息队列、actor 模型)从结构上消除锁。
一、预防:破坏四条件
预防通过破坏四必要条件之一,从结构上保证死锁不可能发生:
- 破坏占有并等待:要求进程在开始前一次性申请它需要的全部资源,拿不全就一个都不要。简单粗暴,但资源利用率低(进程可能长时间占着暂不用的资源),且易导致饥饿(一次要太多资源,永远凑不齐)。
- 破坏不剥夺:进程申请新资源失败时,主动释放已占有的资源(让别人能用);或允许系统抢占资源。代价:实现复杂、回滚已做的工作代价大(如数据库事务)、可能反复申请释放陷入活锁。
- 破坏循环等待(最常用):资源有序分配——给每类资源一个全局编号,进程申请资源必须严格按编号升序(先申请编号小的)。这样请求方向单调递增,不可能形成环。代价:编号设计困难、可能被迫提前占用暂不用的资源、与实际使用顺序不符导致浪费。
- 破坏互斥:改用可共享资源(无锁数据结构、只读文件)。但很多资源(打印机、写锁)天然互斥,根本无法破坏,所以这条极少实用。
预防的通病:以牺牲资源利用率和并发度为代价换取"绝对不死锁",性价比低,通用 OS 几乎不用,主要用于资源种类少、需求确定的实时/嵌入式系统。
二、避免:银行家算法
避免策略不破坏四条件,而是在运行时根据进程的资源需求,判断每次分配是否会进入不安全状态:
- 安全状态:存在一个进程序列
<P1, P2, ..., Pn>,使得每个 Pi 申请的资源都能被"当前可用 + 前面所有进程释放的资源"满足。即能找到一个让所有进程都能跑完的顺序。安全状态一定不死锁。 - 不安全状态:找不到这样的序列。注意:不安全不等于已死锁,只是有死锁风险(万一某个进程真申请了上限资源就会死锁)。避免策略的保守做法是"宁可错杀,不进不安全状态"。
银行家算法(Dijkstra, 1965) 的核心:把系统比作银行——进程是借款人(要资源),OS 是银行家(放贷前要确认放出去后所有人还能周转)。维护四个矩阵:
Available : 各类资源当前剩余可用数(向量)
Max : 每个进程对每类资源的最大需求(矩阵)
Allocation: 每个进程当前已分配到的资源(矩阵)
Need : 每个进程还可能再申请的资源 = Max − Allocation(矩阵)请求处理流程(进程 Pi 申请资源 Request[i]):
1. 若 Request[i] > Need[i]:报错(不能申请超过自己声明的最大需求)
2. 若 Request[i] > Available:Pi 等待(资源不够)
3. 试探性分配:
Available -= Request[i]
Allocation[i] += Request[i]
Need[i] -= Request[i]
4. 跑安全性算法判断新状态是否安全:
- 安全 → 真分配(试探转为正式)
- 不安全 → 回滚试探,Pi 等待安全性算法:找 Need ≤ Available 的进程,假设它完成、归还 Allocation,更新 Available,重复直到所有进程完成(安全)或找不到可满足的进程(不安全)。
- 优点:理论上能避免死锁,资源利用率高于预防(不必提前占满)。
- 缺点:①要求事先知道每个进程的最大需求(现实程序很难预估);②每次分配都要跑算法,进程和资源多时开销大;③资源可能随时增减、进程数动态变化,假设过强。所以通用 OS 不用银行家算法,它主要用于资源种类固定、需求可预知的场景(如某些数据库、批处理系统)。
三、检测与恢复
检测恢复策略允许死锁发生,但周期性检查,发现死锁再处理:
- 死锁检测算法:类似银行家的安全性算法,但用当前的 Allocation(实际持有)而非 Need 上限。当 Available 满足不了任何进程的剩余请求时,剩下的进程即死锁。或者周期性构建资源分配图、检测是否有环(每类资源单实例时)。
- 检测频率:多久检测一次是权衡——太频繁开销大,太少则死锁后系统瘫痪时间长。通常在资源利用率高、或进程长时间阻塞时触发。
- 恢复方法:
- 终止进程:杀掉死锁环上的进程。可选全部终止(一次清干净但代价大)或逐个终止(每次杀一个再检测,直到死锁解除,代价小但需多次检测)。选谁杀?通常选代价最小的(已做工作少、优先级低、持有资源少)。
- 抢占资源:从某些进程强行剥夺资源给死锁进程。需要选牺牲者(victim selection)、把牺牲者回滚到安全点(以便日后重启),实现复杂且可能引发不一致。
- 回滚:把系统状态恢复到某个检查点(checkpoint),让死锁进程从检查点重新执行。
检测恢复适用于死锁偶尔发生、且能承受恢复代价的系统(如数据库的事务死锁检测)。
四、鸵鸟策略:忽略死锁
鸵鸟算法(Ostrich Algorithm)——像鸵鸟遇到危险把头埋进沙子,假装死锁不会发生,什么都不做。
- 理由:通用 OS(Linux/Windows)中死锁发生概率低(用户进程通常不持有多个系统级资源),而预防/避免的代价(性能损失、复杂度、过强假设)远高于死锁本身的损失。权衡后选择忽略。
- 代价:偶尔会死锁,整个系统或某些进程卡死,需要人工重启。
- 谁来负责:把避免死锁的责任转嫁给应用开发者——加锁时注意顺序(锁排序)、用带超时的锁、避免嵌套锁、用更高层并发原语(信号量、消息传递、actor 模型)。
通用 OS 选鸵鸟、实时/嵌入式 OS 选预防或避免、数据库选检测恢复——没有万能策略,按场景权衡。
下一步
死锁的处理策略讲完后,进入参考——四条件速查表、四种策略对比、银行家算法步骤、死锁vs饥饿vs活锁对比、易错点与权威链接。