14 共享数据与竞争条件

核心:多个任务读写同一份数据时,要考虑同步;volatile 不是锁。

1. 为什么 counter++ 会丢失更新?

初始 counter = 0
任务 A:读取 0 ── 计算 1 ────────── 写回 1
任务 B:      读取 0 ── 计算 1 ───── 写回 1
期望:2                              可能结果:1

counter++ 是读 → 修改 → 写回,不能凭这一行代码判断它具有原子性。在有 C11 数据竞争语义的环境里,未同步的冲突访问还可能构成未定义行为。

2. Mutex 的作用

任务 A ── 拿锁 ── 读/改/写 ── 释放锁
任务 B ── 等待 ─────────────── 拿锁 ── 读/改/写 ── 释放锁
// 示意伪代码:实际调用名称取决于 RTOS
mutex_lock();
counter++;
mutex_unlock();

整段读—修改—写都应受同一把锁保护,所有访问者要遵守相同约定。

选择好处代价/限制
Mutex能保护多个任务共享数据可能等待,带来调度及锁开销
无同步直接修改不需要锁开销存在竞争、丢失更新风险

3. ISR 与任务:不要在中断里等普通 Mutex

蓝牙硬件中断
     │
     ▼
ISR:快速处理必要硬件状态 + 发送 ISR 安全通知
     │
     ▼
ISR 退出
     │
     ▼
蓝牙任务:等待通知 → 获取 Mutex → 处理共享数据 → 释放 Mutex

为什么? 如果 ISR 打断了持锁任务,再阻塞等待这把锁,持锁任务可能无法运行去释放它。ISR 通常应快速返回,使用 RTOS 专门提供的 ISR 安全 API 通知任务。通知方式要能匹配事件数量和数据生命周期。

4. volatile vs Mutex:不要混淆

volatileMutex
做什么约束对易变对象访问的编译器优化协调任务进入临界区
能保证 counter++ 原子吗不能不是把操作变原子指令,而是提供互斥保护
典型用途硬件寄存器等RTOS 任务共享状态
volatile int counter;
counter++; // 有 volatile 仍不代表多任务修改安全

一句话总结

  • 竞争:两个任务可能都读旧值,最后丢一次更新。
  • Mutex:让任务排队,保护整个共享操作。
  • ISR:快进快出,通知任务处理,不等待普通 Mutex。
  • volatile:不是同步机制,不能代替 Mutex 或原子操作。

注:示意图及 API 为教学用途,实际代码以芯片、RTOS 的同步原语和 ISR API 为准。