Note
怎么预防和解决死锁?
从破坏死锁四条件出发,说明预防、避免、检测恢复,以及工程上常用的加锁次序与超时策略。
省流
处理死锁通常分几类思路:预防(破坏四个必要条件之一)、避免(分配前判断是否进入不安全状态,如银行家算法)、检测与恢复(允许发生,发现后再解除)、以及工程上最常用的 加锁纪律(统一锁顺序、缩小临界区、尽量避免嵌套锁、使用超时或 try-lock)。
业务代码里最有效、也最常被考察的做法是:所有线程按相同的全局顺序获取多把锁,从结构上消灭循环等待;同时减少锁持有时间,降低“占有并等待”的窗口。数据库场景则常见死锁检测,发现后回滚其中一个事务。
没有单一银弹。选择取决于资源是否可抢占、能否接受回滚、以及检测成本。回答时应先提四条件,再对应到可落地的手段。
一、破坏必要条件(预防)
| 条件 | 可能手段 | 代价/限制 |
|---|---|---|
| 互斥 | 改成无锁数据结构或只读共享 | 并非所有资源都能去掉互斥 |
| 占有并等待 | 一次性申请全部所需资源;或申请前先释放已持有资源 | 利用率下降,实现复杂 |
| 不可抢占 | 允许抢占/回滚已占用资源 | 需要可撤销的状态与恢复协议 |
| 循环等待 | 全局资源编号,按编号升序加锁 | 要能给锁定义稳定顺序 |
其中 统一加锁顺序 是服务端开发面试中最实用的答案。
二、死锁避免
在分配资源前判断:即便满足当前请求,是否仍存在一条能使所有进程完成的分配序列。银行家算法是教材经典例子。它要求事先知道最大需求,实际通用业务系统中直接照搬较少,但“只在安全状态下分配”的思想仍有参考价值。
三、检测与恢复
允许死锁偶尔发生,再处理:
- 检测:维护等待图,查找环;数据库 InnoDB 等会做死锁检测。
- 恢复:回滚代价较低的一方、终止进程、或撤销部分资源占用后重试。
适合事务本身已支持回滚、且死锁不极频繁的场景。
四、工程实践清单
- 固定锁顺序;文档化“必须先 A 后 B”。
- 能合并就用一把锁或更粗粒度资源,避免无意义的锁嵌套。
- 缩小临界区,锁内不做慢 I/O。
- 使用
try_lock或带超时的加锁,失败则释放已持有锁并退避重试,降低永久卡住概率。 - 优先用现成并发组件(队列、channel、读写锁的正确模式),减少手写多锁协议。
- 监控与日志:记录持锁时间、等待图或超时事件,便于确认是否真是死锁。
和“活锁、饥饿”分开说
- 活锁:一直在重试,状态不断变化,但无进展。
- 饥饿:某些线程长期得不到调度或锁。
- 死锁:等待环导致所有相关方停止推进。
对策不同,面试里不要混用术语。