3、死锁的预防策略:破坏四个必要条件之一
死锁这东西,说白了就是几个任务互相掐着对方的资源不放,谁也别想往前走。我在项目中遇到过好几次死锁导致的系统卡死,最严重的一次是工业控制设备直接停机,现场工程师急得团团转。后来我养成了一个习惯:在设计阶段就把死锁扼杀在摇篮里。
怎么扼杀?核心思路就一条——破坏死锁的四个必要条件中的任意一个。这四个条件是:互斥、持有并等待、不可剥夺、循环等待。只要干掉其中一个,死锁就玩不转。
核心思想:死锁预防 = 破坏四个必要条件之一。不需要全部破坏,一个就够。
3.1 四个必要条件回顾
先快速过一下这四个条件,方便后面理解预防策略。
| 条件 | 含义 | 能否破坏? |
|---|---|---|
| 互斥 | 资源一次只能给一个任务用 | 部分资源可以(如共享内存),但打印机、串口这类不行 |
| 持有并等待 | 任务拿着资源A,还等着资源B | 可以!一次性分配法就是干这个的 |
| 不可剥夺 | 资源不能被强行抢走 | 可以,但实现起来有风险 |
| 循环等待 | 任务之间形成资源等待环 | 可以,给资源编号就能破 |
嗯,这里要注意:互斥条件在很多场景下是没法破坏的。比如你让两个任务同时往串口发数据,那不乱套了?所以实际工程中,我们主要盯着后面三个条件下手。
3.2 资源一次性分配法:破坏「持有并等待」
这个方法我个人特别喜欢,因为它简单粗暴。说白了就是:任务在开始运行之前,一次性申请它需要的所有资源。要么全给,要么一个都不给。
举个例子,任务A需要打印机和扫描仪。按照一次性分配法,任务A启动时必须同时拿到打印机和扫描仪。如果扫描仪被占用了,那打印机也不给A,A就乖乖等着。
我的经验:这个方法在小型嵌入式系统里特别好用。我曾经在一个RTOS项目里用这个策略,死锁问题直接清零。但要注意,任务必须提前知道自己需要哪些资源,不能动态变化。
3.3 一次性分配法的实现
实现起来其实不复杂。我习惯用一个资源管理模块,任务启动时调用一个分配函数,传入所有需要的资源ID列表。
/* 资源一次性分配示例 */
typedef struct {
int resource_id;
int is_available;
} Resource;
int request_all_resources(int task_id, int* res_list, int count) {
int i;
/* 先检查所有资源是否可用 */
for (i = 0; i < count; i++) {
if (!is_resource_available(res_list[i])) {
return -1; /* 有一个不可用,就不分配 */
}
}
/* 全部可用,一次性分配 */
for (i = 0; i < count; i++) {
allocate_resource(task_id, res_list[i]);
}
return 0; /* 分配成功 */
}
你看,逻辑很直白。先检查,再分配。中间没有「先拿一个,再等另一个」的窗口期,所以「持有并等待」这个条件就被破坏了。
注意:一次性分配法有个明显的缺点——资源利用率低。任务可能只需要打印机5分钟,但为了等扫描仪,打印机被白白占着半小时。这在资源紧张的系统里是个大问题。
3.4 其他预防策略简介
除了一次性分配法,还有几种常见策略,我简单说一下。
- 破坏「不可剥夺」:允许操作系统强行回收资源。比如任务A占着打印机不放,系统可以把它挂起,把打印机给更高优先级的任务。我曾经在一个实时系统里用过,但实现起来要小心,容易引发优先级反转。
- 破坏「循环等待」:给所有资源编号,任务必须按编号顺序申请。比如打印机编号1,扫描仪编号2,任务必须先申请1再申请2。这样就不会形成循环等待环。
我个人更推荐一次性分配法,因为它实现简单,逻辑清晰。但如果你做的是大型系统,资源很多,那资源编号法可能更合适。
3.5 知识体系图
下面这张图把死锁预防的核心逻辑串起来了。我画图时习惯把重点标红,方便一眼看清。
3.6 避坑指南
我曾经在一个项目里踩过坑,分享出来给大家提个醒。
- 别把所有资源都一次性分配:有些资源是临时需要的,比如一块临时缓冲区。如果任务启动时就把所有缓冲区都占了,其他任务就没法用了。我建议只分配关键资源,临时资源按需申请。
- 注意任务优先级:一次性分配法本身不解决优先级问题。高优先级任务可能因为等低优先级任务释放资源而被阻塞。这时候需要配合优先级继承协议。
- 别在中断里用:中断服务程序里千万别搞资源一次性分配。中断上下文不能阻塞等待,否则系统直接崩。我见过有人这么干,结果就是死机。
我的建议:如果你刚开始做嵌入式系统,先从一次性分配法入手。它简单、可靠,适合资源不多的小系统。等经验丰富了,再尝试资源编号法或者更复杂的策略。
好了,死锁预防就聊到这儿。记住一句话:预防死锁,就是破坏四个必要条件中的一个。一次性分配法是最容易上手的,但也要注意它的局限性。实际项目中,根据资源类型和系统规模选择合适的策略,别死磕一种方法。
公众号:蓝海资料掘金营,微信deep3321