尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
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 信号量信号量用于计数/同步互斥量用于资源互斥
RELATED

相关推荐

Cocos Creator 3.7.2 Shader进阶:打造可交互的动态扫光材质系统

Cocos Creator 3.7.2 Shader进阶:打造可交互的动态扫光材质系统

1. 动态扫光效果的核心原理扫光效果本质上是通过Shader对UV坐标进行动态计算实现的。想象一下用手电筒在黑暗的房间里扫过墙面的场景——光束中心最亮,边缘逐渐衰减。在Shader中,我们通过以下几个关键参数来模拟这个效果:光束中心点(lightCen…

📅 2026/11/10 11:34:16
2026最适合中医理疗馆低成本获客神器:餐宝盈GEO+小程序模式,获客转化一手抓

2026最适合中医理疗馆低成本获客神器:餐宝盈GEO+小程序模式,获客转化一手抓

为什么中医理疗馆现在不能只做小程序,也不能只做流量如果只看单点工具,中医理疗馆做小程序像是在解决承接问题,做 GEO 像是在解决流量问题。但如果顺着真实经营链路往下看,会发现中医理疗馆当前最核心的问题,不是单独缺…

📅 2026/11/10 11:34:15
嵌入式 Linux 高波特率串口丢包全链路解决方案

嵌入式 Linux 高波特率串口丢包全链路解决方案

前言 做嵌入式 Linux 开发,没人能绕开串口调试。 我之前在做工业数据网关项目时,就结结实实栽在了串口丢包上:外接传感器用 921600 波特率连续上报数据,应用层 read 总是随机丢字节,少则几个多则几十字节;降到 115200 就正常,一升高波特率就复现。 前后调了整整半个月:…

📅 2026/10/9 7:54:24
MORE NEWS

更多资讯

📰

中级电工证几级好用 郑州报考避坑指南

中级电工证几级好用 郑州报考避坑指南 你急需特种作业证才能上岗找工作,偏偏对中级电工证几级好用一头雾水,不知道怎么选、怎么考、怎么用?尤其是在郑州这种竞争激烈、政策严格的地区,一个不慎就可能白忙一场。今天咱们就从漯河转岗电工老张的亲身经历说…

📰

没有电工证怎么装灯具?考前押题能帮你上岸

没有电工证怎么装灯具?考前押题能帮你上岸 之前考的证在外省能不能转过来?这是很多电工从业者最头疼的问题。特别是像你这样在濮阳的中小施工企业负责人,经常要面对项目变动、人员调配等现实问题, 电工证异地转移…

📰

聊城电工证过期司鸿豪怎么报名,别再被中介骗了

聊城电工证过期司鸿豪怎么报名,别再被中介骗了 你是不是正为聊城电工证过期司鸿豪怎么报名发愁?怕被中介骗钱、怕流程复杂、怕自己搞不清楚?别急,这期内容专门给你讲清楚,怎么报名、怎么避坑、怎么选择靠谱的渠道,手把手教你搞定。…

📰

广州黄埔电工证哪里考?官方报名入口怎么找不被坑

广州黄埔电工证哪里考?官方报名入口怎么找不被坑 你是不是也这样,手里攥着电工证,却不知道在广州黄埔哪里能考?网上一搜一堆机构,价格五花八门,怕是被中介套路了?别急,我来给你理清楚,广州黄埔电工证到底怎么考,官方报名入口怎么找,还有怎么避坑。…

📰

福建电工证在哪查询电子证书查询

工地太忙根本没时间复习考试?福建电工证在哪考一目了然 工地太忙根本没时间复习考试,很多人想考电工证却不知道从哪下手,更别说福建电工证在哪考了。现在市面上培训机构五花八门,信息真假难辨,搞不好钱花了证还没拿到,还耽误工期。这篇文章就从…

📰

安顺电工证办理怎么考?考前押题+实操攻略全解析

安顺电工证办理怎么考?考前押题+实操攻略全解析 证书过期了不知道怎么复审延期?别慌,今天咱们就从【安顺电工证办理】说起,讲讲怎么在规定时间内完成复审,避免被处罚。特别是像你这样已经在一线干活、证书快到期的电工, 考前押题…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬