死锁就是两个或多个进程/线程互相等待对方释放资源,谁都走不下去,永久卡住。

四个必要条件(Coffman 条件)

缺一不可,同时满足才会死锁:

  1. 互斥(Mutual Exclusion)
    资源同一时刻只能被一个进程占用

  2. 占有并等待(Hold and Wait)
    进程已经拿着至少一个资源,还在等其他资源

  3. 不可抢占(No Preemption)
    资源不能被强制剥夺,只能由持有者主动释放

  4. 循环等待(Circular Wait)
    进程之间形成环形等待链:A 等 B,B 等 C,C 等 A

死锁检测

资源分配图(RAG)

  • 进程 → 资源:请求边
  • 资源 → 进程:分配边
  • 图中有环 = 可能死锁(单实例资源有环必死锁)

银行家算法(检测变体)

维护 Available / Allocation / Need 矩阵,模拟能否找到一个安全序列让所有进程完成。找不到 → 死锁。

实际系统做法

  • 定期运行检测算法
  • 发现死锁后:杀掉代价最小的进程,或回滚事务(数据库常用)

死锁预防

破坏四个条件之一即可:

条件 破坏方式 代价
互斥 用无锁结构(CAS/原子操作) 不是所有资源都能无锁化
占有并等待 一次性申请所有资源,或申请前释放已有资源 资源利用率低
不可抢占 申请失败时强制释放已有资源 实现复杂,可能导致饥饿
循环等待 给资源编号,强制按序申请(最常用) 需要全局规划资源顺序

最实用的方案:资源有序申请

给所有锁/资源编号,所有线程必须按编号从小到大申请,永远不会形成环。

# 错误示范:可能死锁
# 线程1: lock_a -> lock_b
# 线程2: lock_b -> lock_a

# 正确做法:统一按编号顺序申请
# lock_a 编号 1,lock_b 编号 2
# 所有线程都先拿 lock_a,再拿 lock_b
with lock_a:
with lock_b:
# 临界区
pass

死锁避免(顺带一提)

预防是静态限制,避免是动态决策——每次分配前用银行家算法判断是否会进入不安全状态,不安全就拒绝分配。理论上完美,实际上因为需要提前知道最大需求量,工程中很少用。

总结

  • 预防:破坏四个条件之一,最常用是资源有序申请
  • 检测:资源分配图或银行家算法,发现死锁后杀进程或回滚
  • 避免:银行家算法动态判断,理论完美但工程少用