死锁产生的四个必要条件
死锁这东西,说白了就是几个任务互相掐住了对方的命门,谁都不肯松手。我在嵌入式系统里调试过不少死锁问题,每次排查到最后,基本都能归到这四个条件上。你只要把这四个条件记牢了,分析死锁就像拿着地图找路——清晰得很。
条件一:互斥(Mutual Exclusion)
互斥,意思就是资源一次只能被一个任务占用。打个比方,厕所只有一个坑,你进去了别人就得等着。
在操作系统里,互斥资源包括:
- 打印机、串口这类外设
- 共享内存中的某个数据结构
- 全局变量的写操作(如果没加锁)
我记得有一次,一个同事在驱动里直接操作了硬件寄存器,没加任何保护。结果两个任务同时写,寄存器值乱得一塌糊涂。嗯,这就是典型的互斥没做好——但反过来,如果资源本身就不需要互斥,死锁也就无从谈起。
条件二:持有并等待(Hold and Wait)
这个条件的意思是:任务已经占着一个资源,同时还在等另一个资源。而且它不放手。
我习惯用一个场景来理解:你左手拿着一个杯子,右手想去拿另一个杯子。但你不肯把左手的杯子放下,就这么僵着。
在代码里,它长这样:
// 任务A
lock(mutex1); // 持有资源1
// ... 做点事情
lock(mutex2); // 等待资源2
// 此时,如果任务B已经锁了mutex2,死锁就来了
你想想看,如果任务在等待新资源时,能先把已占有的资源释放掉,那死锁就不会发生。但现实往往不允许——因为释放了可能就被别人抢走了。
条件三:不可剥夺(No Preemption)
不可剥夺,意思是资源一旦被任务占有,除非任务自己主动释放,否则谁也抢不走。
这其实是一种保护机制。你想想,如果A正在用打印机打印,突然被B强行抢走,那打印出来的东西肯定乱七八糟。所以操作系统通常不允许强制剥夺资源。
但问题也出在这里——如果A占着资源不撒手,B就只能干等。等多久?不知道。如果A自己也被别的资源卡住了,那就彻底死锁了。
我遇到过这样一个案例:一个低优先级任务锁了共享缓冲区,然后被高优先级任务抢占了CPU。高优先级任务也想锁这个缓冲区,结果发现锁被低优先级任务拿着,而低优先级任务又因为被抢占无法继续执行。说白了,这就是不可剥夺带来的连锁反应。
条件四:循环等待(Circular Wait)
循环等待,就是多个任务之间形成了一个等待环。A等B,B等C,C等A。环一旦形成,谁也跑不掉。
我习惯用一张图来理解:
这张图里,每个任务都持有一个资源,同时又在等待另一个任务持有的资源。箭头转了一圈,回到了起点——这就是循环等待。
我个人习惯在排查死锁时,先画出资源分配图。如果发现环,那基本就锁定问题了。剩下的就是看这个环是怎么形成的。
四个条件的总结
| 条件 | 含义 | 打破方法 |
|---|---|---|
| 互斥 | 资源一次只能被一个任务使用 | 使用共享资源(如只读数据) |
| 持有并等待 | 任务占着资源不放手,同时等别的资源 | 一次申请所有资源,或申请失败时释放已占资源 |
| 不可剥夺 | 资源不能被强制抢走 | 允许资源被剥夺(需谨慎) |
| 循环等待 | 多个任务形成等待环 | 给资源编号,按顺序申请 |
这四个条件,说白了就是死锁的“配方”。你只要在代码里看到它们同时出现,那死锁基本跑不掉。反过来,你写代码时能刻意避免其中任意一个,死锁就不会发生。
嗯,这四个条件就讲到这里。记住它们,分析死锁问题的时候,你会发现自己思路清晰很多。