ucos(九)互斥锁和死锁
一、概述
互斥锁,亦称:互斥信号量。
在编程中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个任务(线程)访问该对象(任务之间访问到相同的函数、相同的全局变量)。某个任务得到互斥锁后,就可以访问共享资源,其他任务等待该任务释放互斥锁才能进行访问。
何时可以用普通信号量替代互斥锁?如果没有任务对共享资源访问有截止的时间,那么普通信号量可以替代互斥锁;反之则必须使用互斥锁。因为前者会造成无界优先级反转,后者却不会。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | void task( void *parg) { while (1) { 加锁 访问共享资源 解锁(立即) ..... 加锁 访问共享资源 解锁(立即) .... } } |
二、函数接口
1.创建互斥锁
1 2 3 | void OSMutexCreate (OS_MUTEX *p_mutex, CPU_CHAR *p_name, OS_ERR *p_err) |
参数:
- p_mutex,互斥锁对象
- p_name,互斥锁名字
- p_err,返回错误码,没有错误的就返回OS_ERR_NONE
返回值:无
2.等待互斥锁
若等待成功,则锁定共享资源
1 | void OSMutexPend (OS_MUTEX *p_mutex, OS_TICK timeout, OS_OPT opt, CPU_TS *p_ts, OS_ERR *p_err) |
参数:
- p_mutex,互斥锁对象
- timeout,超时时间,默认写0,一直等待
- opt,设置当前等待互斥锁的阻塞方式,默认写OS_OPT_PEND_BLOCKING,阻塞等待。如果互斥锁此时被另外一个任务占用,且指定的阻塞类型为OS_OPT_PEND_NON_BLOCKING,则OSMutexPend就会直接返回而不再等待互斥锁被释放。
- p_ts,用于记录等待互斥锁花了多长时间,默认写NULL,不记录。
- p_err,返回错误码,没有错误的就返回OS_ERR_NONE
说明:
- 如果占有互斥锁是一个较低优先级多任务,那么UCOSIII就会临时提升它的优先级,使得其等于此时想要获取互斥锁的任务优先级。
3.释放互斥锁,解锁
1 | void OSMutexPost (OS_MUTEX *p_mutex, OS_OPT opt, OS_ERR *p_err) |
参数:
- p_mutex,互斥锁对象
- opt,释放互斥锁后希望其他等待锁的任务(最高优先级且就绪)得到立即执行,填写参数OS_OPT_POST_NONE,也是默认值。若使用了OS_OPT_POST_NO_SCHED这个参数,得到互斥锁的任务不会立即执行。
- p_err,返回错误码,没有错误的就返回OS_ERR_NONE。
三、死锁(或抱死)
死锁(dead lock)也称做抱死(deadly embrace),指两个任务无限制地互相等待对方控制着的资源。
假设任务T1正独占资源R1,任务T2正独占资源R2,示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | void T1( void *parg) { while (1) { (1)等待事件发生 (2)请求互斥锁M1 (3)访问共享资源R1 : : (4)------- 中断! : : (8)请求互斥锁M2 (9)访问共享资源R2 } } void T2( void *parg) { while (1) { 等待事件发生 (5)请求互斥锁M2 (6)访问共享资源R2 : : (7)请求互斥锁M1 访问共享资源R1 } } |
(1)假设任务T1具有最高优先级,且其等待的事件发生了,所以任务1开始运行。
(2)任务T1运行并请求获得互斥锁M1
(3)任务T1获得M1并访问共享资源R1
(4)一个中断发生了,导致具有比任务T1更高优先级的T2获得了CPU的使用权。
(5)该中断是任务T2等待的事件,故任务T2继续运行。
(6)任务T2继续运行,请请求获得互斥锁M2以访问共享资源R2。
(7)任务T2想要获得互斥锁M1,但此时UCOSIII知道此时M1被任务T1占用着。
(8)任务T2无法继续运行,UCOSIII做任务切换转而运行任务T1.
(9)任务T1想要获取互斥锁M2,但M2却被任务T2占有了。此时两个任务便死锁了,谁也无法继续运行,因为谁也无法获取对方的资源。
避免出现死锁的方法,让每个任务都:
- 先得到全部需要的资源,再做下一个动作
- 用相同的顺序申请多个资源
- 在调用请求互斥锁的函数时设定超时时间
以相同的顺序先得到全部需要的资源来避免死锁的问题,示例代码1如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | void T1( void *parg) { while (1) { 等待事件发生 请求互斥锁M1 请求互斥锁M2 访问共享资源R1 访问共享资源R2 释放互斥锁M1 释放互斥锁M2 } } void T2( void *parg) { while (1) { 等待事件发生 请求互斥锁M1 请求互斥锁M2 访问共享资源R1 访问共享资源R2 释放互斥锁M1 释放互斥锁M2 } } |
以相同的顺序获取资源来避免死锁的问题,
示例代码2如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | void T1( void *parg) { while (1) { 等待事件发生 请求互斥锁M1 访问共享资源R1 释放互斥锁M1 请求互斥锁M2 访问共享资源R2 释放互斥锁M2 } } void T2( void *parg) { while (1) { 等待事件发生 请求互斥锁M1 访问共享资源R1 释放互斥锁M1 请求互斥锁M2 访问共享资源R2 释放互斥锁M2 } } |
四、实例例程
1、死锁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 | #include "sys.h" #include "delay.h" #include "usart.h" #include "led.h" #include "includes.h" //任务1控制块 OS_TCB Task1_TCB; void task1( void *parg); CPU_STK task1_stk[128]; //任务1的任务堆栈,大小为128字,也就是512字节 //任务2控制块 OS_TCB Task2_TCB; void task2( void *parg); CPU_STK task2_stk[128]; //任务2的任务堆栈,大小为128字,也就是512字节 OS_MUTEX g_mutex_1; //互斥锁1 OS_MUTEX g_mutex_2; //互斥锁1 void res1( void ) { volatile uint32_t i=0x50; while (i--) { delay_ms(10); } } void res2( void ) { volatile uint32_t i=0x50; while (i--) { delay_ms(10); } } //主函数 int main( void ) { OS_ERR err; systick_init(); //时钟初始化 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //中断分组配置 usart_init(9600); //串口初始化 LED_Init(); //LED初始化 //OS初始化,它是第一个运行的函数,初始化各种的全局变量,例如中断嵌套计数器、优先级、存储器 OSInit(&err); //创建任务1 OSTaskCreate( (OS_TCB *)&Task1_TCB, //任务控制块,等同于线程id (CPU_CHAR *) "Task1" , //任务的名字,名字可以自定义的 (OS_TASK_PTR)task1, //任务函数,等同于线程函数 ( void *)0, //传递参数,等同于线程的传递参数 (OS_PRIO)6, //任务的优先级6 (CPU_STK *)task1_stk, //任务堆栈基地址 (CPU_STK_SIZE)128/10, //任务堆栈深度限位,用到这个位置,任务不能再继续使用 (CPU_STK_SIZE)128, //任务堆栈大小 (OS_MSG_QTY)0, //禁止任务消息队列 (OS_TICK)0, //默认时间片长度 ( void *)0, //不需要补充用户存储区 (OS_OPT)OS_OPT_TASK_NONE, //没有任何选项 &err //返回的错误码 ); if (err!=OS_ERR_NONE) { printf ( "task 1 create fail\r\n" ); while (1); } //创建任务2 OSTaskCreate( (OS_TCB *)&Task2_TCB, //任务控制块 (CPU_CHAR *) "Task2" , //任务的名字 (OS_TASK_PTR)task2, //任务函数 ( void *)0, //传递参数 (OS_PRIO)6, //任务的优先级6 (CPU_STK *)task2_stk, //任务堆栈基地址 (CPU_STK_SIZE)128/10, //任务堆栈深度限位,用到这个位置,任务不能再继续使用 (CPU_STK_SIZE)128, //任务堆栈大小 (OS_MSG_QTY)0, //禁止任务消息队列 (OS_TICK)0, //默认时间片长度 ( void *)0, //不需要补充用户存储区 (OS_OPT)OS_OPT_TASK_NONE, //没有任何选项 &err //返回的错误码 ); if (err!=OS_ERR_NONE) { printf ( "task 2 create fail\r\n" ); while (1); } //创建互斥锁1 OSMutexCreate(&g_mutex_1, "g_mutex_1" ,&err); OSMutexCreate(&g_mutex_2, "g_mutex_2" ,&err); //启动OS,进行任务调度 OSStart(&err); printf ( ".......\r\n" ); while (1); } void task1( void *parg) { OS_ERR err; printf ( "task1 is create ok\r\n" ); while (1) { OSMutexPend(&g_mutex_1,0,OS_OPT_PEND_BLOCKING,NULL,&err); printf ( "[task1]access res1 begin\r\n" ); res1(); printf ( "[task1]access res1 end\r\n" ); OSMutexPend(&g_mutex_2,0,OS_OPT_PEND_BLOCKING,NULL,&err); printf ( "[task1]access res2 begin\r\n" ); res2(); printf ( "[task1]access res2 end\r\n" ); OSMutexPost(&g_mutex_1,OS_OPT_POST_NONE,&err); OSMutexPost(&g_mutex_2,OS_OPT_POST_NONE,&err); } } void task2( void *parg) { OS_ERR err; printf ( "task2 is create ok\r\n" ); while (1) { OSMutexPend(&g_mutex_2,0,OS_OPT_PEND_BLOCKING,NULL,&err); printf ( "[task2]access res2 begin\r\n" ); res2(); printf ( "[task2]access res2 end\r\n" ); OSMutexPend(&g_mutex_1,0,OS_OPT_PEND_BLOCKING,NULL,&err); printf ( "[task2]access res1 begin\r\n" ); res1(); printf ( "[task2]access res1 end\r\n" ); OSMutexPost(&g_mutex_1,OS_OPT_POST_NONE,&err); OSMutexPost(&g_mutex_2,OS_OPT_POST_NONE,&err); } } |
执行结果:
由打印信息可知,程序处于死锁状态。

2、避免死锁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 | #include "sys.h" #include "delay.h" #include "usart.h" #include "led.h" #include "includes.h" //任务1控制块 OS_TCB Task1_TCB; void task1( void *parg); CPU_STK task1_stk[128]; //任务1的任务堆栈,大小为128字,也就是512字节 //任务2控制块 OS_TCB Task2_TCB; void task2( void *parg); CPU_STK task2_stk[128]; //任务2的任务堆栈,大小为128字,也就是512字节 OS_MUTEX g_mutex_1; //互斥锁1 OS_MUTEX g_mutex_2; //互斥锁1 void res1( void ) { volatile uint32_t i=0x50; while (i--) { delay_ms(10); } } void res2( void ) { volatile uint32_t i=0x50; while (i--) { delay_ms(10); } } //主函数 int main( void ) { OS_ERR err; systick_init(); //时钟初始化 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //中断分组配置 usart_init(9600); //串口初始化 LED_Init(); //LED初始化 //OS初始化,它是第一个运行的函数,初始化各种的全局变量,例如中断嵌套计数器、优先级、存储器 OSInit(&err); //创建任务1 OSTaskCreate( (OS_TCB *)&Task1_TCB, //任务控制块,等同于线程id (CPU_CHAR *) "Task1" , //任务的名字,名字可以自定义的 (OS_TASK_PTR)task1, //任务函数,等同于线程函数 ( void *)0, //传递参数,等同于线程的传递参数 (OS_PRIO)6, //任务的优先级6 (CPU_STK *)task1_stk, //任务堆栈基地址 (CPU_STK_SIZE)128/10, //任务堆栈深度限位,用到这个位置,任务不能再继续使用 (CPU_STK_SIZE)128, //任务堆栈大小 (OS_MSG_QTY)0, //禁止任务消息队列 (OS_TICK)0, //默认时间片长度 ( void *)0, //不需要补充用户存储区 (OS_OPT)OS_OPT_TASK_NONE, //没有任何选项 &err //返回的错误码 ); if (err!=OS_ERR_NONE) { printf ( "task 1 create fail\r\n" ); while (1); } //创建任务2 OSTaskCreate( (OS_TCB *)&Task2_TCB, //任务控制块 (CPU_CHAR *) "Task2" , //任务的名字 (OS_TASK_PTR)task2, //任务函数 ( void *)0, //传递参数 (OS_PRIO)6, //任务的优先级6 (CPU_STK *)task2_stk, //任务堆栈基地址 (CPU_STK_SIZE)128/10, //任务堆栈深度限位,用到这个位置,任务不能再继续使用 (CPU_STK_SIZE)128, //任务堆栈大小 (OS_MSG_QTY)0, //禁止任务消息队列 (OS_TICK)0, //默认时间片长度 ( void *)0, //不需要补充用户存储区 (OS_OPT)OS_OPT_TASK_NONE, //没有任何选项 &err //返回的错误码 ); if (err!=OS_ERR_NONE) { printf ( "task 2 create fail\r\n" ); while (1); } //创建互斥锁1 OSMutexCreate(&g_mutex_1, "g_mutex_1" ,&err); OSMutexCreate(&g_mutex_2, "g_mutex_2" ,&err); //启动OS,进行任务调度 OSStart(&err); printf ( ".......\r\n" ); while (1); } void task1( void *parg) { OS_ERR err; printf ( "task1 is create ok\r\n" ); while (1) { OSMutexPend(&g_mutex_1,0,OS_OPT_PEND_BLOCKING,NULL,&err); OSMutexPend(&g_mutex_2,0,OS_OPT_PEND_BLOCKING,NULL,&err); printf ( "[task1]access res1 begin\r\n" ); res1(); printf ( "[task1]access res1 end\r\n" ); printf ( "[task1]access res2 begin\r\n" ); res2(); printf ( "[task1]access res2 end\r\n" ); OSMutexPost(&g_mutex_1,OS_OPT_POST_NONE,&err); OSMutexPost(&g_mutex_2,OS_OPT_POST_NONE,&err); } } void task2( void *parg) { OS_ERR err; printf ( "task2 is create ok\r\n" ); while (1) { OSMutexPend(&g_mutex_1,0,OS_OPT_PEND_BLOCKING,NULL,&err); OSMutexPend(&g_mutex_2,0,OS_OPT_PEND_BLOCKING,NULL,&err); printf ( "[task2]access res2 begin\r\n" ); res2(); printf ( "[task2]access res2 end\r\n" ); printf ( "[task2]access res1 begin\r\n" ); res1(); printf ( "[task2]access res1 end\r\n" ); OSMutexPost(&g_mutex_1,OS_OPT_POST_NONE,&err); OSMutexPost(&g_mutex_2,OS_OPT_POST_NONE,&err); } } |
执行结果:
task1执行完毕,task2执行,如此循环..。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)