同步互斥与通信-有缺陷的同步示例FreeRTOS笔记
同步互斥与通信-有缺陷的同步示例FreeRTOS笔记
- 同步与互斥的概念
- 1. 同步(Synchronization)
- 2. 互斥(Mutual Exclusion)
- 3. 代码示例分析
- **问题:死循环等待**
- 4. 死循环的效率问题
- 5. 总结
- **关键点**
同步与互斥的概念
1. 同步(Synchronization)
定义:同步是指多个任务/线程之间按照特定的顺序执行,确保某些操作的依赖关系被满足。
本质:协调任务的执行顺序,避免因顺序不当导致的错误。
2. 互斥(Mutual Exclusion)
定义:互斥是指多个任务/线程不能同时访问共享资源,确保资源的独占性。
本质:防止多个任务同时修改共享数据,避免竞态条件(Race Condition)。
例子:
一句话理解同步与互斥:我等你用完厕所,我再用厕所。
什么叫同步?就是:哎哎哎,我正在用厕所,你等会。
什么叫互斥?就是:哎哎哎,我正在用厕所,你不能进来。
同步与互斥经常放在一起讲,是因为它们之的关系很大,“互斥”操作可以使用“同步”来实现。我“等”你用完厕所,我再用厕所。这不就是用“同步”来实现“互斥”吗?
再举一个例子:
在团队活动里,同事A先写完报表,经理B才能拿去向领导汇报。经理B必须等同事A完成报表,AB之间有依赖,B必须放慢脚步,被称为同步。在团队活动中,同事A已经使用会议室了,经理B也想使用,即使经理B是领导,他也得等着,这就叫互斥。经理B跟同事A说:你用完会议室就提醒我。这就是使用"同步"来实现"互斥"。
同步:在代码中,任务B需要等待任务A完成某个操作(如计算结果)后才能继续执行。(使用信号量等机制)
互斥:在代码中,多个任务不能同时修改同一个全局变量或硬件寄存器。(使用互斥锁(Mutex)、二进制信号量等机制)
3. 代码示例分析
问题:死循环等待
// 任务A:计算
int global_flag = 0;
void taskA() {// 执行计算global_flag = 1; // 标记完成
}// 任务B:等待任务A完成
void taskB() {while (global_flag == 0) { // 死循环等待// 空转,浪费CPU}// 使用计算结果
}
问题原因:
- 编译器优化可能导致
global_flag
的值被缓存在寄存器中,任务B无法及时读取到内存中的新值。 - 死循环会持续占用CPU资源,效率低下。
解决方案:
-
使用
volatile
关键字:volatile int global_flag = 0;
- 确保每次访问
global_flag
都直接从内存读取,避免编译器优化。
- 确保每次访问
-
使用同步机制替代死循环:
- 用信号量或互斥锁替代忙等待:
SemaphoreHandle_t sem = xSemaphoreCreateBinary();void taskA() {// 执行计算xSemaphoreGive(sem); // 通知任务B完成 }void taskB() {xSemaphoreTake(sem, portMAX_DELAY); // 阻塞等待,不浪费CPU// 使用计算结果 }
- 用信号量或互斥锁替代忙等待:
4. 死循环的效率问题
- 忙等待(Busy Waiting):
- 任务B在等待时不断轮询变量,导致CPU资源浪费。
- 在RTOS中,应避免忙等待,改用阻塞式等待(如信号量、事件标志)。
- RTOS调度特点:
- RTOS会根据任务的优先级和状态(就绪、阻塞、挂起)调度CPU时间。
- 如果任务B阻塞等待,RTOS可以将CPU分配给其他任务,提高系统效率。
5. 总结
概念 | 核心目的 | 典型机制 | 应用场景 |
---|---|---|---|
同步 | 协调任务的执行顺序 | 信号量、条件变量 | 依赖关系(如任务B等任务A) |
互斥 | 防止多个任务同时访问资源 | 互斥锁、二进制信号量 | 共享资源保护(如全局变量) |
优化问题 | 避免编译器优化导致的变量问题 | volatile 关键字 | 多任务共享变量 |
关键点
- 同步 ≠ 互斥:同步关注顺序,互斥关注资源独占。
- 避免忙等待:用阻塞式等待替代死循环,提高系统效率。
- 正确使用
volatile
:确保共享变量的内存可见性,避免编译器优化错误。