FreeRTOS下单任务写多任务读变量是否需要互斥锁/信号量保护?
问题解答
首先给明确结论:你这个场景不是必须用互斥锁,也完全没必要强上信号量做同步,要不要做访问保护,核心看三个点:变量x的位宽、所用MCU架构的内存访问原子性、业务对读取值一致性的要求。
完全不需要加锁的情况
如果同时满足下面几个条件,直接裸读写一点问题都没有:
- 变量
x的位宽不超过当前架构的自然字长,CPU对该位宽的内存读写是单指令完成的原子操作。比如最常用的Cortex-M全系列32位MCU,uint32_t、int32_t、布尔值、指针这类32位及以下宽度的类型,读写只需要一条汇编指令,根本不会出现读一半被写任务打断、读到半新半旧错误值的情况。 - 业务上能接受读任务偶尔读到更新前的旧值。你写任务才1秒更新一次,只要读到旧值不会导致逻辑错误,完全不需要强同步。
- 给变量
x加volatile关键字修饰,避免编译器把变量优化到寄存器缓存,导致读任务永远读不到更新后的值。注意volatile只解决编译器优化带来的可见性问题,不保证访问原子性,别搞混两者作用。
拿最常见的Cortex-M内核MCU举例,如果你定义的是
volatile uint32_t x;,写任务每秒给x赋一次值,其他任务随便读,不会出任何问题。这种场景加互斥锁纯属于过度设计,平白增加开销,还引入优先级反转、死锁的潜在风险。
需要做访问保护的情况
只要碰到下面任意一种情况,就需要做访问保护,但也不是必须用互斥锁/信号量:
- 变量
x是超过架构自然字长的类型:比如32位MCU上的64位整数、软浮点实现的双精度浮点数、结构体、多字节数组,这类变量的写操作需要多条汇编指令才能完成,读任务有可能刚好卡在两条指令中间被调度,读到高字节是新值、低字节是旧值的错乱数据。 - 业务逻辑要求读任务必须第一时间读到x更新后的最新值,完全不能接受旧值:比如x是紧急停机标志,一旦置位所有任务必须立刻响应,这种场景除了保证访问原子性,还需要做线程间同步。
- 所用架构本身不保证单字访问的原子性:比如部分8位、16位MCU,哪怕是16位变量都需要分两次读写,哪怕位宽小也可能读到错值。
单写多读场景的轻量替代方案
别一碰到共享变量就想着互斥锁、信号量,单写多读场景有开销小得多的方案:
- 对超字长的普通变量,直接用临界区保护就行:写x前后关个中断,整个写操作也就几条指令,关中断时间几微秒,比互斥锁的上下文切换开销小太多;读的时候怕读到错值,也可以进临界区读,读完立刻开中断。
- 对一致性要求高的大变量,可以用版本号校验法:写操作前先给版本号加1,写完新值再把版本号加1;读的时候先读版本号,读完x之后再核对一次版本号,如果两次读的版本号是偶数且相等,说明读到的是完整的有效值,否则就重读,全程不需要加任何锁。
- 如果是带原子操作指令的32位/64位高端MCU,直接用编译器提供的原子变量接口,连临界区都不用进。
最后纠正一个常见误区:很多人背教条说“多任务访问共享变量必须加锁”,本质是没搞懂锁的作用——锁是用来把多条指令组成的临界区包成原子操作,如果你的读写本身就是硬件层面原子的,加锁完全是多此一举。你这个场景写频率才1Hz,实时压力极小,只要符合前面说的无锁条件,直接裸跑就行。
内容的提问来源于stack exchange,提问作者Ankit Bansal
相关产品推荐
相关产品推荐

