“双锁反序”(AB-BA Deadlock)是多线程开发中最经典的死锁模型。当两个线程以相反的顺序请求两把相同的锁时,就会形成环路等待。
1. 场景描述
- 线程 1:已经持有 锁 A,正在尝试获取 锁 B。
- 线程 2:已经持有 锁 B,正在尝试获取 锁 A。
2. Wait-for Graph (等待图)
在等待图中,节点代表线程,有向边代表等待关系。
- 节点 T1:代表线程 1。
- 节点 T2:代表线程 2。
- 边 T1 -> T2:表示 T1 正在等待 T2 释放资源(锁 B)。
- 边 T2 -> T1:表示 T2 正在等待 T1 释放资源(锁 A)。
结论:图中出现了一个闭环(Cycle) ,这是死锁存在的充分必要条件。
3. 资源分配图 (Resource Allocation Graph)
为了更清晰地看到“锁”在其中的位置,我们可以使用资源分配图。
- 持有关系(Solid Line) :锁 A -> 线程 1,锁 B -> 线程 2。
- 请求关系(Dashed Line) :线程 1 -> 锁 B,线程 2 -> 锁 A。
4. 如何在代码中规避?
针对这种死锁,通常有三种修复策略:
- 固定加锁顺序:强制所有线程必须先获取 A 再获取 B。
- 使用
tryLock:如果获取第二把锁失败,则主动释放已持有的第一把锁,回退重试,破坏“不可剥夺”条件。 - 合并锁:如果 A 和 B 总是同时被使用,考虑将它们合并为一个更大粒度的锁。
在您之前提到的 Objective-C Runtime 内部,为了防止这类死锁,苹果工程师在源码中对 runtimeLock 和 cacheUpdateLock 的获取顺序有着极其严格的规定。