1. 课程导入
在嵌入式开发中,我们经常面对这样的需求:一个 MCU 既要每 10ms 采集一次传感器数据,又要响应串口命令,还要驱动显示屏刷新,并在异常时立即切断电机。如果把这些工作全部塞进一个 while(1) 主循环,代码会迅速演变成难以维护的“时间片面条”。并发编程正是为了解决这类多任务共享处理器、共享外设、共享内存的问题而存在。
本课程的目标不是单纯讲解 C++ 语法,而是建立一套可在资源受限设备上落地的并发与任务架构能力:能识别并发需求,能选择合适模型,能写出可预测、可测试、可复盘的嵌入式并发代码。
一、核心概念:并发、并行与实时性
并发(Concurrency)指多个任务在逻辑上同时推进;并行(Parallelism)指多个任务在物理上同时执行。单核 MCU 上通常只有并发,没有并行,靠调度器快速切换制造“同时运行”的假象。理解这一点,才能明白为什么共享资源的保护如此重要。
嵌入式并发还多了一个硬约束:实时性。任务不仅要“做完”,还要“按时做完”。因此我们需要关注优先级、阻塞时间、抖动和截止期,而不仅仅是平均吞吐量。
二、整体知识框架
本课程的知识框架可以分成四层:语言层、RTOS 层、架构层和验证层。语言层提供 std::thread、std::mutex、std::atomic、std::condition_variable 等工具;RTOS 层提供任务、队列、信号量、事件组;架构层决定任务如何划分、如何通信、如何降级;验证层则通过日志、断言、静态分析和压力测试保证行为可预测。
三、典型应用场景
嵌入式并发并非抽象概念,它出现在大量真实产品中。下表列出常见场景与对应的并发关注点:
| 应用场景 | 并发任务 | 关键问题 |
|---|---|---|
| 电机控制 | PWM 更新、电流采样、故障保护 | 高优先级保护必须抢占,共享 ADC 数据需原子访问 |
| 工业网关 | Modbus 采集、MQTT 上报、本地缓存 | 队列背压、网络阻塞不能拖垮采集任务 |
| 智能穿戴 | 传感器融合、BLE 通信、屏幕刷新 | 低功耗与实时响应之间的平衡 |
| 车载控制器 | CAN 收发、诊断、执行器控制 | 优先级反转、死锁、时序可预测性 |
四、一个最小并发案例
下面是一个典型的“采集-处理”双任务结构。采集任务以固定周期读取传感器,处理任务从队列取数据。注意:队列是任务间通信的边界,也是共享资源保护的第一道防线。
// 伪代码:FreeRTOS 风格的双任务结构
QueueHandle_t g_sensorQueue;
void TaskAcquire(void *pv) {
SensorData d;
for (;;) {
d = read_sensor(); // 周期采集
xQueueSend(g_sensorQueue, &d, 0); // 非阻塞入队
vTaskDelay(pdMS_TO_TICKS(10));
}
}
void TaskProcess(void *pv) {
SensorData d;
for (;;) {
if (xQueueReceive(g_sensorQueue, &d, portMAX_DELAY)) {
process(d); // 消费数据
}
}
}
这段代码看似简单,却已经包含了并发编程的核心问题:任务优先级如何设置?队列满了怎么办?process() 执行时间过长会不会导致采集丢数据?这些问题将在后续章节逐一展开。
五、常见误区与复盘
- 误区一:任务越多越好。任务数量增加会带来栈内存开销和调度抖动,嵌入式系统应尽量合并职责相近的任务。
- 误区二:用全局变量传数据。不加保护的全局变量是竞态条件的温床,应优先使用队列、邮箱或原子类型。
- 误区三:忽视优先级反转。低优先级任务持有互斥量时,可能阻塞高优先级任务,需使用优先级继承或设计上避免长临界区。
- 误区四:只在实验室测试。并发缺陷往往在高负载、长时间运行后出现,必须做压力测试和时序测量。