知识卡片
死锁的成因与三种预防策略
内容
死锁是悲观锁特有的问题:Martin锁住了Customer文件、David锁住了Order文件,随后Martin发现自己也需要Order(被David锁着)、David发现自己也需要Customer(被Martin锁着)——双方互相等待,谁都无法继续,除非其中一个放弃。死锁在包含很多参与者的复杂链条里会变得更棘手、更难察觉。处理死锁大致分两类思路:一类是事后处理已经发生的死锁——软件检测死锁后选一个”牺牲者”,放弃其工作和已加的锁(死锁检测本身很难做,对牺牲者也很痛苦);或者给每个锁设超时时间,到期自动失效、工作作废(比死锁检测容易实现,但代价是可能把一个原本没有死锁、只是持锁时间长的人误判为牺牲者)。另一类是从源头上防止死锁发生——死锁的根源是”已经持有一个锁的人还想再申请更多锁”,因此可以强制所有人一开始工作就必须获取全部可能需要的锁,之后不允许再申请新锁;具体实现可以是强制按固定顺序(如字母顺序)获取锁,排序靠后的一方自动成为让步的”牺牲者”;也可以更简单粗暴——谁后申请、谁就自动放弃。最稳妥的做法是把这些手段叠加使用(比如”一开始锁光所有需要的资源”加上”超时兜底”),像同时系腰带和背带一样保守,因为死锁太让人头疼、太容易出错——对大多数企业开发者而言,宁可选用简单保守、偶尔产生不必要牺牲者的方案,也不要冒着漏判某个死锁场景的风险。
结构图:
flowchart TB
A["死锁处理策略"]
A --> B["事后处理"]
B --> B1["死锁检测+选牺牲者<br/>检测难/对牺牲者痛苦"]
B --> B2["锁超时失效<br/>易实现但可能误判牺牲者"]
A --> C["事前预防<br/>禁止已持锁后再申请新锁"]
C --> C1["固定加锁顺序<br/>如按字母序"]
C --> C2["后申请者自动让步"]
A --> D["保守组合策略<br/>预防+超时兜底"]
参考来源
- 位置:《企业应用架构模式》第一部分"表述"之"第5章 并发"之"5.4.2 死锁"(源文件:_epub-src/OEBPS/Text/000034.html)
- 结论依据:原文说明"死锁出现的原因是人们在已经得到锁的情况下还希望得到更多的锁……防止死锁的方法就是强制人们在开始工作的时候就获得所有可能需要的锁,在此之后就不允许再得到更多的锁。可以硬性规定每个人获取锁的顺序……也可以这样规定:如果Martin想要获得一个锁,而David已经有了一个,Martin就会自动成为牺牲者……可以强制所有人都在开始的时候就获取全部可能需要的锁,再加上时间限制,以防出现什么意外",直接支撑本卡结构图。
- 原始内容:所以对于企业应用开发者来说,最好还是使用那些简单而保守的模式。虽然很容易产生不必要的牺牲者,但是这总比那些由于忽略某个死锁场景的后果要好得多。