RT-Thread快速入门(七)之互斥量
2026/7/13 15:02:52 网站建设 项目流程

前言

在上篇文章的信号量示例中,我们把信号量初始值设为 1,用做"二值信号量",也能实现互斥访问

线程A take(信号量) → 使用资源 → release(信号量) 线程B take(信号量) → 阻塞等待 → …… → 被唤醒

但这有一个隐患:任何线程都可以 release 这个信号量,包括不是它持有者的第三方线程。这就好比你把厕所门锁了,外面的人却能把手伸进来开锁——那锁还有什么意义?

互斥量(Mutex)正是为了解决这个问题而生的。它的核心规则是:

谁上锁,就只能由谁开锁。


目录

前言

一、为什么要互斥?

1.1 临界资源

1.2 什么情况下需要互斥?

二、互斥量的特性

2.1 互斥量 vs 二值信号量

2.2 互斥量的核心优势

三、优先级反转与优先级继承

3.1 什么是优先级反转?

3.2 优先级继承 — RT-Thread 的解决方案

四、互斥量 API

4.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 读出了a=1(还没写回),这时线程 B 抢占了 CPU,也读了a=1,然后加 8 写回 →a=9。接着 A 继续执行,它手里的值还是 1,加 8 写回 →a=9

期望结果是 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:低优先级(15)

  • MPThread:中优先级(14)

  • HPThread:高优先级(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 被唤醒 → 获得互斥量 → 执行

优先级继承 = 低优先级线程临时"借用"高优先级线程的优先级,以便尽快释放互斥量。


四、互斥量 API

4.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) 先运行,设置标志后休眠 3ms13
BMPThread(14) 运行,休眠 1ms14
CLPThread(15) 获得互斥量,开始运行15
DMPThread 休眠结束 → 抢占 LPThread14 vs 15
EHPThread 休眠结束 → 需要互斥量 → 失败 →提升LPThread优先级LPThread: 15→13
FLPThread(13) 变成最高优先级 → 继续运行 → 释放互斥量 →恢复原优先级,唤醒 HPThreadLPThread: 13→15
GHPThread(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 信号量信号量用于计数/同步,互斥量用于资源互斥

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询