相互等待如何形成

设甲线程先取得锁 A,乙线程先取得锁 B;甲接着等待 B,乙接着等待 A。如果双方都必须取得第二把锁才能完成工作并释放第一把锁,就形成无法自行解除的等待环。每把锁可能都正确地实现互斥,程序仍然失去整体进展。

类似结构也会出现在数据库事务中:两个事务分别锁住不同记录,再请求对方占有的记录。死锁不是普通的“等待时间比较长”,关键在于解除等待所需的动作又依赖于等待中的参与者。存在可能导致死锁的代码,也不表示每次运行都会发生死锁,实际交错决定是否进入该状态。

参考:[2] [4] [1]

资源模型中的四个条件

经典资源死锁分析列出四个必要条件:资源需要互斥占用;执行者占有部分资源时等待其他资源;已分配资源不能被强行夺走;等待依赖形成环。破坏其中任一条件,就能排除这一模型中的死锁。

这些条件用于分析结构,而不是把任何等待都标记为死锁。例如仅有互斥锁并不足以说明已经死锁。实际系统还要明确资源种类、可用实例和等待关系,不能脱离模型仅凭一个抽象图形就断言整个系统的状态。

参考:[1]

预防、避免与恢复

预防通过约束使用规则破坏必要条件,例如要求所有参与者按一致顺序取得多把锁,消除循环等待。避免则利用任务可能请求哪些资源等信息,选择不会导致死锁的调度或分配安排;这种方法需要额外知识,也可能牺牲并发程度。

检测与恢复允许死锁出现,再识别它并采取措施。PostgreSQL 会自动检测死锁,并中止一个相关事务,使其余事务能够推进;应用不能假定总是同一个事务被选中。被中止的工作可能需要重新执行,恢复机制不是把未完成操作直接变成成功,也不能替代对锁顺序的设计。

参考:[1] [4]

与活锁和饥饿的区别

活锁中,执行者仍在采取动作,却因相互响应而没有有效进展。例如双方不断释放已取得的锁并立即重试,可能反复干扰。它与双方阻塞等待的死锁不同,但从任务完成的角度同样构成问题;只把阻塞改成忙重试,不是完整解决方案。

饥饿则指某个执行者长期得不到所需资源或机会,其他执行者却可能继续完成工作。死锁、活锁和饥饿都属于进展问题,但诊断与处理不同。系统仍有处理器活动,或仍有部分请求成功,都不足以证明所有参与者能够继续。

参考:[3] [1]