RT-Thread快速入门(七)之互斥量

📅 2026/7/13 15:03:00
RT-Thread快速入门(七)之互斥量
前言在上篇文章的信号量示例中我们把信号量初始值设为 1用做二值信号量也能实现互斥访问线程A take(信号量) → 使用资源 → release(信号量) 线程B take(信号量) → 阻塞等待 → …… → 被唤醒但这有一个隐患任何线程都可以 release 这个信号量包括不是它持有者的第三方线程。这就好比你把厕所门锁了外面的人却能把手伸进来开锁——那锁还有什么意义互斥量Mutex正是为了解决这个问题而生的。它的核心规则是谁上锁就只能由谁开锁。目录前言一、为什么要互斥1.1 临界资源1.2 什么情况下需要互斥二、互斥量的特性2.1 互斥量 vs 二值信号量2.2 互斥量的核心优势三、优先级反转与优先级继承3.1 什么是优先级反转3.2 优先级继承 — RT-Thread 的解决方案四、互斥量 API4.1 创建/初始化4.2 删除/脱离4.3 获取/释放五、完整示例示例1互斥量基本使用示例2优先级反转与继承演示六、互斥量使用注意事项七、IPC 全家桶总结1~7篇八、总结一、为什么要互斥1.1 临界资源在多线程系统中如果多个线程同时访问同一个资源就可能出问题/* 两个线程同时调用这个函数结果会怎样 */ static int a 1; ​ void add_a(void) { a a 8; // 内部三步①读原值 ②加8 ③写回 }如果线程 A 读出了a1还没写回这时线程 B 抢占了 CPU也读了a1然后加 8 写回 →a9。接着 A 继续执行它手里的值还是 1加 8 写回 →a9。期望结果是 17实际结果是 9。这就是典型的非原子操作导致的竞态问题。1.2 什么情况下需要互斥场景例子说明访问外设串口打印两个线程同时打印信息混杂在一起修改变量a a 8读-改-写三步可能被中断访问结构体设置多个成员设置到一半被抢占结构体状态不一致函数重入使用全局变量的函数多个线程同时调用全局变量互相覆盖这些需要被独占访问的资源称为临界资源。访问临界资源的那段代码称为临界区。二、互斥量的特性2.1 互斥量 vs 二值信号量对比项二值信号量互斥量Mutex初始值1/01谁可以释放任何线程/ISR都可以只有持有者可以释放中断中可用✅ 可以❌不可用优先级继承❌ 不支持✅支持解决优先级反转本质计数器取 0 或 1特殊的二值信号量拥有者标记2.2 互斥量的核心优势信号量线程A take → 串口打印 → 线程B 也能 release → 线程A 被强制解锁互斥量线程A take → 串口打印 → 只有线程A 能 release → 别人无法解锁互斥量就像一把带指纹识别的锁——只有上锁的人才能打开。三、优先级反转与优先级继承3.1 什么是优先级反转假设有三个线程LPThread低优先级15MPThread中优先级14HPThread高优先级13正常预期HPThread(13) MPThread(14) LPThread(15) 实际可能 ① LPThread 先获得互斥量 ② HPThread 也想获得互斥量 → 被阻塞LPThread 不释放 ③ MPThread 不需要互斥量但优先级比 LPThread 高 → MPThread 一直运行LPThread 无法执行 → LPThread 无法释放互斥量 → HPThread 被 LPThread 堵住又被 MPThread 间接堵住结果最高优先级的 HPThread被中优先级的 MPThread 间接阻塞——这叫优先级反转。3.2 优先级继承 — RT-Thread 的解决方案RT-Thread 的互斥量内置了优先级继承机制① LPThread 获得互斥量 → 优先级 15 ② HPThread 也想获得互斥量 → 失败 → HPThread 把 LPThread 的优先级提升到自己一样 → LPThread 的优先级从 15 提升到 13 ③ LPThread 变成最高优先级 → 立刻执行 ④ LPThread 释放互斥量 → 恢复自己的优先级 15 ⑤ HPThread 被唤醒 → 获得互斥量 → 执行优先级继承 低优先级线程临时借用高优先级线程的优先级以便尽快释放互斥量。四、互斥量 API4.1 创建/初始化/* 动态创建 */ rt_mutex_t rt_mutex_create(const char *name, rt_uint8_t flag); ​ /* 静态初始化 */ rt_err_t rt_mutex_init(rt_mutex_t mutex, const char *name, rt_uint8_t flag);⚠️注意flag参数已废除均按RT_IPC_FLAG_PRIO处理优先级继承必须基于优先级排序。4.2 删除/脱离rt_err_t rt_mutex_delete(rt_mutex_t mutex); // 动态删除 rt_err_t rt_mutex_detach(rt_mutex_t mutex); // 静态脱离4.3 获取/释放/* 获取互斥量可阻塞 */ rt_err_t rt_mutex_take(rt_mutex_t mutex, rt_int32_t time); ​ /* 尝试获取不阻塞 */ rt_err_t rt_mutex_trytake(rt_mutex_t mutex); ​ /* 释放互斥量只能由持有者调用 */ rt_err_t rt_mutex_release(rt_mutex_t mutex);五、完整示例示例1互斥量基本使用本程序创建 2 个发送线程通过宏USE_MUTEX切换是否使用互斥量观察串口打印是否混杂。#include rtthread.h ​ #define USE_MUTEX 1 // 1使用互斥量0不使用 ​ static rt_mutex_t dynamic_mutex RT_NULL; static rt_thread_t thread1 RT_NULL; static rt_thread_t thread2 RT_NULL; ​ #define THREAD_PRIORITY 15 #define THREAD_STACK_SIZE 512 #define THREAD_TIMESLICE 10 ​ /* 发送线程入口函数两个线程共用 */ static void thread_entry(void *parameter) { rt_uint32_t i; ​ while(1) { #ifdef USE_MUTEX /* 使用互斥量完整打印 0~19不会被其他线程打断 */ rt_mutex_take(dynamic_mutex, RT_WAITING_FOREVER); ​ for(i 0; i 20; i) rt_kprintf(%s: i%d\n, (char *)parameter, i); ​ rt_mutex_release(dynamic_mutex); #else /* 不使用互斥量打印到一半可能被其他线程打断 */ for(i 0; i 20; i) rt_kprintf(%s: i%d\n, (char *)parameter, i); #endif ​ rt_thread_mdelay(10); } } ​ int main(void) { /* 1. 创建动态互斥量 */ dynamic_mutex rt_mutex_create(dmutex, RT_IPC_FLAG_FIFO); if (dynamic_mutex RT_NULL) { rt_kprintf(rt_mutex_create error.\n); return -1; } ​ /* 2. 创建发送线程1 */ thread1 rt_thread_create(thread1, thread_entry, thread1, THREAD_STACK_SIZE, THREAD_PRIORITY, THREAD_TIMESLICE); if (thread1 ! RT_NULL) rt_thread_startup(thread1); ​ /* 3. 创建发送线程2 */ thread2 rt_thread_create(thread2, thread_entry, thread2, THREAD_STACK_SIZE, THREAD_PRIORITY, THREAD_TIMESLICE); if (thread2 ! RT_NULL) rt_thread_startup(thread2); ​ return 0; }运行结果对比未用互斥量 用互斥量不使用实验现象如下图左边所示线程 1、线程 2 的打印信息没有混在 一起线程1打印0~7还未到19就被线程2打断使用实验现象如下图右边所示线程1和线程2依次打印0~19中途 不会相互打断示例2优先级反转与继承演示创建三个线程演示优先级继承机制#include rtthread.h ​ static rt_mutex_t dynamic_mutex RT_NULL; static rt_thread_t LPThread RT_NULL; // 低优先级 15 static rt_thread_t MPThread RT_NULL; // 中优先级 14 static rt_thread_t HPThread RT_NULL; // 高优先级 13 ​ #define THREAD_PRIORITY 15 #define THREAD_STACK_SIZE 512 #define THREAD_TIMESLICE 10 ​ /* 低优先级线程获得互斥量然后被 HPThread 提升优先级 */ static void lpthread_entry(void *parameter) { rt_kprintf(LPThread: get mutex\n); ​ rt_mutex_take(dynamic_mutex, RT_WAITING_FOREVER); ​ /* 此时的优先级可能已被 HPThread 提升 */ rt_kprintf(LPThread: do something\n); rt_thread_mdelay(5); ​ rt_mutex_release(dynamic_mutex); rt_kprintf(LPThread: release mutex\n); } ​ /* 中优先级线程不需要互斥量 */ static void mpthread_entry(void *parameter) { rt_kprintf(MPThread: run\n); rt_thread_mdelay(2); // 短暂运行后挂起 } ​ /* 高优先级线程需要互斥量 */ static void hpthread_entry(void *parameter) { rt_kprintf(HPThread: try to get mutex\n); ​ rt_mutex_take(dynamic_mutex, RT_WAITING_FOREVER); rt_kprintf(HPThread: got mutex\n); ​ rt_mutex_release(dynamic_mutex); } ​ int main(void) { /* 1. 创建动态互斥量 */ dynamic_mutex rt_mutex_create(dmutex, RT_IPC_FLAG_FIFO); if (dynamic_mutex RT_NULL) return -1; ​ /* 2. 创建三个优先级不同的线程 */ LPThread rt_thread_create(LPThread, lpthread_entry, RT_NULL, THREAD_STACK_SIZE, THREAD_PRIORITY, // 15 THREAD_TIMESLICE); if (LPThread ! RT_NULL) rt_thread_startup(LPThread); ​ MPThread rt_thread_create(MPThread, mpthread_entry, RT_NULL, THREAD_STACK_SIZE, THREAD_PRIORITY - 1, // 14 THREAD_TIMESLICE); if (MPThread ! RT_NULL) rt_thread_startup(MPThread); ​ HPThread rt_thread_create(HPThread, hpthread_entry, RT_NULL, THREAD_STACK_SIZE, THREAD_PRIORITY - 2, // 13 THREAD_TIMESLICE); if (HPThread ! RT_NULL) rt_thread_startup(HPThread); ​ return 0; }执行流程时序事件优先级变化AHPThread(13) 先运行设置标志后休眠 3ms13BMPThread(14) 运行休眠 1ms14CLPThread(15) 获得互斥量开始运行15DMPThread 休眠结束 → 抢占 LPThread14 vs 15EHPThread 休眠结束 → 需要互斥量 → 失败 →提升LPThread优先级LPThread: 15→13FLPThread(13) 变成最高优先级 → 继续运行 → 释放互斥量 →恢复原优先级唤醒 HPThreadLPThread: 13→15GHPThread(13) 获得互斥量执行完毕13六、互斥量使用注意事项注意事项说明❌不能在中断中使用ISR 中不可调用rt_mutex_take/release✅只能由持有者释放其他线程无法强制释放✅支持优先级继承自动解决优先级反转问题✅嵌套取锁同一个线程可以多次take但需要相同次数的release⚠️尽快释放持有互斥量期间不要做耗时操作七、IPC 全家桶总结1~7篇到现在为止我们已经学完了 RT-Thread 的所有核心 IPC 机制文章内容传递什么特点一线程创建与删除—线程基础二优先级与时间片—调度规则三延时函数与空闲线程—让出 CPU四消息队列任意大小数据灵活链表实现五邮箱4 字节整数/指针轻量数组实现六信号信号编号软中断异步通知六信号量1 个计数值计数/同步七互斥量—资源互斥优先级继承八、总结知识点要点互斥量特殊的二值信号量自带所有权标记核心规则谁 take谁 release别人无法释放动态创建rt_mutex_create(name, flag)— flag 已废除静态初始化rt_mutex_init(mutex, name, flag)获取rt_mutex_take()可阻塞/rt_mutex_trytake()不阻塞释放rt_mutex_release()— 只能是持有者调用删除/脱离rt_mutex_delete()/rt_mutex_detach()⚠️ 中断中不可使用互斥量✅ 优先级继承自动解决优先级反转问题互斥量 vs 信号量信号量用于计数/同步互斥量用于资源互斥