【第22期】通讯变革:全局变量 vs 消息队列 (Queue)

核心差异:全局变量是**“裸奔”的数据共享,甚至可能读到一半被改写;消息队列(Queue)是“线程安全”**的管道,自带同步与缓冲功能。

1. 全局变量的隐形杀手:脏读 (Dirty Read)

假设我们有一个结构体,包含两个变量,由 任务A(采集) 写入,任务B(显示) 读取。

struct SensorData {
    int x;
    int y;
} g_data;

// 任务A:采集传感器坐标
void Task_Sensor() {
    g_data.x = 100;
    // <--- 就在这一瞬间,高优先级的任务B抢占了! --->
    g_data.y = 200;
}

// 任务B:显示坐标
void Task_Display() {
    printf("X=%d, Y=%d", g_data.x, g_data.y);
}

事故推演

  1. 旧数据是 {x=0, y=0}

  2. 任务A 刚把 x 更新为 100

  3. 任务B 抢占执行,读取数据。它读到了 x=100y=0

  4. 结果:任务B 拿到了一个现实中从未存在过的坐标 {100, 0}

这就是 脏读。数据的一致性被破坏了。在裸机里,我们靠关中断来保护;而在 RTOS 里,我们有了更高级的工具——队列


2. 消息队列:不仅仅是数组

消息队列 (Queue) 是 RTOS 中最基本、最重要的通讯机制。你可以把它想象成一个传送带

  • 生产者 (Producer):把数据放入传送带入口。

  • 消费者 (Consumer):从传送带出口拿走数据。

为什么它比全局变量好?

  1. 原子性 (Atomicity):RTOS 保证数据入队和出队的操作是完整的。任务B 永远不会从队列里读到“半个结构体”。

  2. 数据拷贝 (Copy by Value):当你发送数据时,RTOS 通常会把数据内容复制到队列的内存区中。就算任务A 里的局部变量销毁了,队列里的数据依然有效。

  3. 缓冲 (Buffering):如果任务A 生产得快,任务B 消费得慢,队列可以暂存这些数据(比如深度为 10),防止数据丢失。

代码形态:

// 定义一个队列句柄
QueueHandle_t xQueue;

// 任务A (发送)
void Task_Sender(void *pvParam) {
    int value = 0;
    while(1) {
        value++;
        // 把 value 的值复制进队列
        xQueueSend(xQueue, &value, 0); 
        vTaskDelay(100);
    }
}

// 任务B (接收)
void Task_Receiver(void *pvParam) {
    int buffer;
    while(1) {
        // 从队列读数据。
        // 核心魔法:如果队列是空的,任务B会自动阻塞 (睡觉),不占CPU!
        // 一旦有数据入队,任务B 瞬间醒来处理。
        xQueueReceive(xQueue, &buffer, portMAX_DELAY);
        
        printf("Received: %d", buffer);
    }
}

3. 自带的“流量控制”功能

除了传递数据,队列还有一个裸机全局变量无法比拟的功能:阻塞同步

场景:串口接收任务(快)往队列里塞数据,SD卡写入任务(慢)从队列取数据写文件。

  • 如果队列满了

    • 裸机数组:直接覆盖旧数据(丢包),或者指针乱飞导致溢出。

    • RTOS队列xQueueSend 可以选择阻塞。如果队列满了,发送任务会进入睡眠,暂停生产,等待接收任务腾出空间。这天然实现了一个流量控制阀,防止下游任务被冲垮。

  • 如果队列空了

    • 裸机变量:接收者必须不断轮询检查 if (flag == 1),浪费 CPU。

    • RTOS队列:接收任务阻塞睡眠,完全不耗电。


4. 什么时候不用队列?

队列虽然好,但也不是万能的。 由于涉及到 内存拷贝内核调度,队列的操作比直接读写全局变量要慢得多(微秒级 vs 纳秒级)。

  • 大块数据传输(如摄像头图像)不要把几百 KB 的图片直接塞进队列!这会把内存撑爆且拷贝太慢。

    • 正确做法:把图片的指针 (Address) 塞进队列。消费者拿到钥匙(指针)后,直接去仓库(内存)取货。

  • 极高频的数据(如 20kHz 电机环路):队列开销太大。这种场景下,依然会小心翼翼地使用全局变量配合互斥量关中断


关键点总结

  1. 全局变量:快,但危险。容易发生脏读,缺乏同步机制。

  2. 消息队列:安全,解耦。实现了“生产者-消费者”模型,自带缓冲和阻塞唤醒机制。

  3. 原则:任务间传命令、传状态、传少量数据,首选队列。

/******************************************************
 * 本文为作者《嵌入式开发基础与工程实践》系列文章之一。
 * 关注即可订阅后续内容更新,采用异步推送机制,无需主动轮询。
 * 转发本文可视为一次网络广播,有助于更多节点接收该信息。
 ******************************************************/

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值