/*
  程序: Tasks之间数据传递
        有多任务同时写入,或者数据大小超过cpu内存通道时,或者对共享资源的访问时候,需要有防范机制
        使用MUTEX对数据对Cirtical Section的内容进行保护
        可以想象成MUTEX就是一把锁

  公众号:孤独的二进制

  语法:
  SemaphoreHandle_t xHandler; 创建Handler
  xHandler = xSemaphoreCreateMutex(); 创建一个MUTEX 返回NULL,或者handler
  xSemaphoreGive(xHandler); 释放
  xSemaphoreTake(xHanlder, timeout); 指定时间内获取信号量 返回pdPASS, 或者pdFAIL

  理解方法:
  MUTEX的工作原理可以想象成
  共享的资源被锁在了一个箱子里,只有一把钥匙,有钥匙的任务才能对改资源进行访问
*/

// 养成良好习惯,被多进程和中断调用的变量使用 volatile 修饰符
volatile uint32_t inventory = 100; //总库存
volatile uint32_t retailCount = 0; //线下销售量
volatile uint32_t onlineCount = 0; //线上销售量

SemaphoreHandle_t xMutexInventory = NULL; //创建信号量Handler

TickType_t timeOut = 1000; //用于获取信号量的Timeout 1000 ticks


void retailTask(void *pvParam) {
  while (1) {

    // 在timeout的时间内如果能够获取就继续
    // 通俗一些:获取钥匙
    if (xSemaphoreTake(xMutexInventory, timeOut) == pdPASS) {
      //被MUTEX保护的内容叫做 Critical Section


      //以下实现了带有随机延迟的 inventory减1;
      //等效为 inventory--; retailCount++;
      uint32_t inv = inventory;
      for (int i; i < random(10, 100); i++) vTaskDelay(pdMS_TO_TICKS(i));
      if (inventory > 0) {
        inventory = inv - 1;
        retailCount++;

        //释放钥匙
        xSemaphoreGive(xMutexInventory);
      } else {
        //无法获取钥匙
      }


    };

    vTaskDelay(100); //老板要求慢一些,客户升级后,可以再加快速度
  }
}

void onlineTask(void *pvParam) {
  while (1) {

    // 在timeout的时间内如果能够获取二进制信号量就继续
    // 通俗一些:获取钥匙
    if (xSemaphoreTake(xMutexInventory, timeOut) == pdPASS) {
      //被MUTEX保护的内容叫做 Critical Section
      //以下实现了带有随机延迟的 inventory减1;
      //等效为 inventory--; retailCount++;
      uint32_t inv = inventory;
      for (int i; i < random(10, 100); i++) vTaskDelay(pdMS_TO_TICKS(i));
      if (inventory > 0) {
        inventory = inv - 1;
        onlineCount++;

        //释放钥匙
        xSemaphoreGive(xMutexInventory);
      } else {
        //无法获取钥匙
      }
    };

    vTaskDelay(100); //老板要求慢一些,客户升级后,可以再加快速度
  }
}


void showTask(void *pvParam) {
  while (1) {

    printf("Inventory : %d\n", inventory);
    printf("  Retail : %d, Online : %d\n", retailCount, onlineCount);


    if (inventory == 0 ) {
      uint32_t totalSales = retailCount + onlineCount;
      printf("-----SALES SUMMARY-----\n");
      printf("  Total Sales:  %d\n", totalSales);
      printf("  OverSales:  %d\n", 100 - totalSales);
    }
    vTaskDelay(pdMS_TO_TICKS(1000));
  }
}

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);

  xMutexInventory = xSemaphoreCreateMutex(); //创建MUTEX

  if (xMutexInventory == NULL) {
    printf("No Enough Ram, Unable to Create Semaphore.");
  } else {
    xTaskCreate(onlineTask,
                "Online Channel",
                1024 * 4,
                NULL,
                1,
                NULL);
    xTaskCreate(retailTask,
                "Retail Channel",
                1024 * 4,
                NULL,
                1,
                NULL);
    xTaskCreate(showTask,
                "Display Inventory",
                1024 * 4,
                NULL,
                1,
                NULL);
  }

}

void loop() {
}

好,这段代码确实不太好懂,因为它同时涉及了 FreeRTOS 多任务 + 互斥锁(Mutex)+ 临界区 + 随机延时制造竞争
我按**“先讲故事 → 再拆代码 → 再指出坑”**的方式给你讲一遍,你一定能懂。


一、先用「人话」讲这程序在干嘛

👉 这是一个“库存抢购”的模拟程序

  • 只有 100 件库存 inventory

  • 两个销售渠道

    • 线下卖(retailTask

    • 线上卖(onlineTask

  • 三个任务同时运行

    1. 线下卖货任务

    2. 线上卖货任务

    3. 显示库存任务

⚠️ 问题:
两个任务同时卖货,如果不加锁,会发生“超卖 / 数据错乱”

所以:
👉 用 Mutex(互斥锁)来保护库存


二、核心概念先搞懂(非常关键)

1️⃣ 什么是 Mutex(互斥锁)

Mutex = 一把钥匙
共享资源 = 一个上锁的箱子(库存)

  • 拿到钥匙的任务 👉 才能动库存

  • 没钥匙的任务 👉 必须等

FreeRTOS 中:

xSemaphoreTake(xMutex, timeout); // 拿钥匙
xSemaphoreGive(xMutex);          // 还钥匙

2️⃣ 什么是 Critical Section(临界区)

if (xSemaphoreTake(xMutexInventory, timeOut) == pdPASS) {
    // 👇👇👇 这里就是临界区
    inventory--;
    retailCount++;
    // 👆👆👆
    xSemaphoreGive(xMutexInventory);
}

👉 被 Mutex 保护的代码块就叫临界区


三、全局变量:为什么要 volatile

volatile uint32_t inventory = 100;
volatile uint32_t retailCount = 0;
volatile uint32_t onlineCount = 0;

原因:

  • 这些变量

    • 多个任务

    • 随时可能被修改

  • 防止编译器优化导致:

    • 任务 A 看不到任务 B 改的值

📌 口诀:

多任务 / 中断 / 共享变量 → volatile


四、Mutex 创建与参数

SemaphoreHandle_t xMutexInventory = NULL;
TickType_t timeOut = 1000; // 1000 个 tick 内等锁
xMutexInventory = xSemaphoreCreateMutex();
  • 成功:返回一个 句柄(Handle)

  • 失败:返回 NULL(内存不够)


五、重点:retailTask(线下卖货)逐行讲

1️⃣ 死循环(任务一直跑)

while (1) {

2️⃣ 尝试拿锁(钥匙)

if (xSemaphoreTake(xMutexInventory, timeOut) == pdPASS) {

含义:

  • 最多等 1000 tick

  • 拿到锁 → 进入临界区

  • 拿不到 → 什么也不做


3️⃣ 临界区(真正卖货)

uint32_t inv = inventory;

📌 先保存一份库存(模拟现实中的“读库存”)


4️⃣ 故意制造混乱(重点!)

for (int i; i < random(10, 100); i++)
    vTaskDelay(pdMS_TO_TICKS(i));

👉 这是作者故意写的

目的:

  • 拉长任务执行时间

  • 增大两个任务同时访问库存的概率

  • 用来证明:没有 Mutex 一定会出问题


5️⃣ 真正扣库存

if (inventory > 0) {
    inventory = inv - 1;
    retailCount++;

含义:

  • 还有货才卖

  • 库存 -1

  • 线下销量 +1


6️⃣ 释放锁(非常重要)

xSemaphoreGive(xMutexInventory);

⚠️ 不释放 = 死锁(其他任务永远进不来)


7️⃣ 任务休息一下

vTaskDelay(100);

不然会一直疯狂抢锁


六、onlineTask:和 retailTask 一模一样

唯一差别:

onlineCount++;

👉 一个卖线上,一个卖线下,本质一样


七、showTask:显示任务(⚠️ 有隐患)

printf("Inventory : %d\n", inventory);
printf("  Retail : %d, Online : %d\n", retailCount, onlineCount);

这里没有加 Mutex

可能导致:

  • 显示到一半,库存被改

  • 打印的数据不一致

📌 严谨写法应该是:

xSemaphoreTake(xMutexInventory, portMAX_DELAY);
// 打印
xSemaphoreGive(xMutexInventory);

八、setup() 做了什么

Serial.begin(115200);

初始化串口


xMutexInventory = xSemaphoreCreateMutex();

创建锁


xTaskCreate(onlineTask, ...);
xTaskCreate(retailTask, ...);
xTaskCreate(showTask, ...);

创建三个并发任务


九、这程序想教你的“真正核心”

🔥 不是卖货,而是:

1️⃣ 多任务同时访问共享数据会出问题
2️⃣ Mutex 可以保护共享资源
3️⃣ 临界区一定要:

  • 尽量短

  • 必须释放锁
    4️⃣ volatile 不是线程安全,只是“可见性”


十、我帮你一句话总结(考试/面试级)

本程序通过 FreeRTOS 的 Mutex 机制,对多个任务访问共享库存变量的临界区进行保护,避免了由于任务并发执行导致的数据竞争和库存超卖问题。

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐