在 ESP32 的 ESP-IDF 框架中,任务同步可以通过 FreeRTOS 提供的多种机制实现,包括信号量(Semaphore)、互斥锁(Mutex)、队列(Queue) 和 事件组(Event Group)。线程的同步方式有很多种,其核心思想都是:在访问临界区的时候只允许一个 (或一类) 线程运行。
1. 信号量(Semaphore)
信号量用于任务间的资源计数或事件通知。分为 二进制信号量(Binary Semaphore) 和 计数信号量(Counting Semaphore)。
适用场景
- 二进制信号量:单次事件触发(如中断通知任务)。
- 计数信号量:资源池管理(如限制同时访问某资源的任务数)。
适用场景:任务 A 完成某个事件后,通知任务 B 继续执行(事件触发型同步)。
核心逻辑:信号量初始为 “空”(不可用),任务 B 等待信号量(阻塞);任务 A 完成事件后释放信号量(变为 “满”),任务 B 被唤醒。
API 函数
// 创建二进制信号量
SemaphoreHandle_t xSemaphoreCreateBinary();
// 创建计数信号量
SemaphoreHandle_t xSemaphoreCreateCounting(UBaseType_t uxMaxCount, UBaseType_t uxInitialCount);
// 获取信号量(阻塞)
xSemaphoreTake(SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait);
// 释放信号量
xSemaphoreGive(SemaphoreHandle_t xSemaphore);
测试代码(二进制信号量)
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semstream.h"
#include "freertos/semphr.h"
SemaphoreHandle_t bin_sem;
void task_sender(void *pvParam)
{
while (1)
{
printf("Sender: 发送信号\n");
xSemaphoreGive(bin_sem); // 释放信号量
vTaskDelay(2000 / portTICK_PERIOD_MS);
}
}
void task_receiver(void *pvParam)
{
while (1)
{
if (xSemaphoreTake(bin_sem, pdMS_TO_TICKS(5000)) == pdTRUE)
{
printf("Receiver: 收到信号\n");
}
else
{
printf("Receiver: 超时未收到信号\n");
}
}
}
void app_main()
{
bin_sem = xSemaphoreCreateBinary();
xTaskCreate(task_sender, "sender", 2048, NULL, 5, NULL);
xTaskCreate(task_receiver, "receiver", 2048, NULL, 5, NULL);
}
2. 互斥锁(Mutex)
互斥锁用于保护共享资源,防止多任务同时访问导致数据竞争。
适用场景
-保护全局变量、硬件外设(如 SPI、I2C)等共享资源,确保同一时间仅一个任务操作资源。
核心逻辑:互斥量初始为 “未锁定”,任务访问资源前需获取互斥量(锁定),完成后释放(解锁);未获取到互斥量的任务会阻塞。
API 函数
// 创建互斥锁
SemaphoreHandle_t xSemaphoreCreateMutex();
// 获取锁
xSemaphoreTake(xSemaphore, portMAX_DELAY);
// 释放锁
xSemaphoreGive(xSemaphore);
测试代码(保护共享变量)
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
SemaphoreHandle_t mutex;
int shared_counter = 0;
void task_increment(void *pvParam)
{
while (1)
{
xSemaphoreTake(mutex, portMAX_DELAY); // 获取锁
shared_counter++;
printf("Task %d: Counter = %d\n", (int)pvParam, shared_counter);
xSemaphoreGive(mutex); // 释放锁
vTaskDelay(100 / portTICK_PERIOD_MS);
}
}
void app_main()
{
mutex = xSemaphoreCreateMutex();
xTaskCreate(task_increment, "task1", 2048, (void*)1, 5, NULL);
xTaskCreate(task_increment, "task2", 2048, (void*)2, 5, NULL);
}
3. 队列(Queue)
队列用于任务间的数据传输,支持 FIFO 或 LIFO 模式。
适用场景
任务间传递结构化数据(如传感器数据、控制命令),即任务间 传递数据 并隐含同步(发送方发送数据后,接收方被唤醒处理)。
核心逻辑:队列是线程安全的 FIFO 缓冲区;发送方将数据入队(若队列满则阻塞),接收方从队列取数据(若队列空则阻塞)。
API 函数
// 创建队列
QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength, UBaseType_t uxItemSize);
// 发送数据
xQueueSend(QueueHandle_t xQueue, const void *pvItemToQueue, TickType_t xTicksToWait);
// 接收数据
xQueueReceive(QueueHandle_t xQueue, void *pvBuffer, TickType_t xTicksToWait);
测试代码(传递整数)
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
QueueHandle_t data_queue;
void task_producer(void *pvParam)
{
int value = 0;
while (1)
{
printf("Producing: %d\n", value);
xQueueSend(data_queue, &value, portMAX_DELAY); // 发送数据
value++;
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
void task_consumer(void *pvParam)
{
int received;
while (1)
{
if(xQueueReceive(data_queue, &received, pdMS_TO_TICKS(2000)) )
{
printf("Consumed: %d\n", received);
}
else
{
printf("队列超时\n");
}
}
}
void app_main()
{
data_queue = xQueueCreate(5, sizeof(int)); // 队列长度5,存储整数
xTaskCreate(task_producer, "producer", 2048, NULL, 5, NULL);
xTaskCreate(task_consumer, "consumer", 2048, NULL, 5, NULL);
}
4. 事件组(Event Group)
事件组通过位掩码实现多任务间的事件同步,允许任务等待多个事件中的任意一个或全部。
适用场景
多任务协同等待多个条件触发(如网络连接成功 + 传感器就绪)。如任务 B 需要等待 多个独立事件 全部 / 部分完成(如等待传感器数据、网络连接、用户输入)。
核心逻辑:事件组用一个 32 位变量(事件位)表示多个事件状态;任务 A/B/C 分别设置对应事件位,任务 D 等待指定事件位组合(全部置位 / 任意一个置位)。
API 函数
// 创建事件组
EventGroupHandle_t xEventGroupCreate();
// 设置事件位
xEventGroupSetBits(EventGroupHandle_t xEventGroup, EventBits_t uxBitsToSet);
// 等待事件位
EventBits_t xEventGroupWaitBits(
EventGroupHandle_t xEventGroup,
EventBits_t uxBitsToWaitFor,
BaseType_t xClearOnExit, // pdTRUE: 等待后清除位
BaseType_t xWaitForAllBits, // pdTRUE: 等待所有位
TickType_t xTicksToWait
);
测试代码(等待多个事件)
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
EventGroupHandle_t event_group;
#define BIT_NET_CONNECTED (1 << 0)
#define BIT_SENSOR_READY (1 << 1)
void task_network(void *pvParam)
{
vTaskDelay(2000 / portTICK_PERIOD_MS);
printf("网络连接成功\n");
xEventGroupSetBits(event_group, BIT_NET_CONNECTED);
vTaskDelete(NULL);
}
void task_sensor(void *pvParam)
{
vTaskDelay(3000 / portTICK_PERIOD_MS);
printf("传感器初始化完成\n");
xEventGroupSetBits(event_group, BIT_SENSOR_READY);
vTaskDelete(NULL);
}
void task_main(void *pvParam)
{
EventBits_t bits = xEventGroupWaitBits(
event_group,
BIT_NET_CONNECTED | BIT_SENSOR_READY,
pdTRUE, // 清除事件位
pdTRUE, // 等待所有位
portMAX_DELAY
);
printf("所有条件就绪,开始执行主任务\n");
vTaskDelete(NULL);
}
void app_main()
{
event_group = xEventGroupCreate();
xTaskCreate(task_network, "network", 2048, NULL, 5, NULL);
xTaskCreate(task_sensor, "sensor", 2048, NULL, 5, NULL);
xTaskCreate(task_main, "main_task", 2048, NULL, 5, NULL);
}
5. 完整测试代码
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h" // 信号量/互斥量
#include "freertos/event_groups.h" // 事件组
#include "freertos/queue.h" // 队列
//---------------------------------------------------------
SemaphoreHandle_t bin_sem;
void task_sender(void *pvParam)
{
while (1)
{
printf("Sender: 发送信号\n");
xSemaphoreGive(bin_sem); // 释放信号量
vTaskDelay(2000 / portTICK_PERIOD_MS);
}
}
void task_receiver(void *pvParam)
{
while (1)
{
if (xSemaphoreTake(bin_sem, pdMS_TO_TICKS(5000)) == pdTRUE) // portMAX_DELAY : 永久等待
{
printf("Receiver: 收到信号\n");
}
else
{
printf("Receiver: 超时未收到信号\n");
}
}
}
//---------------------------------------------------------
SemaphoreHandle_t mutex;
int shared_counter = 0;
void task_increment(void *pvParam)
{
while (1)
{
xSemaphoreTake(mutex, portMAX_DELAY); // 获取锁
shared_counter++;
printf("Task %d: Counter = %d\n", (int)pvParam, shared_counter);
xSemaphoreGive(mutex); // 释放锁
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
//---------------------------------------------------------
EventGroupHandle_t event_group;
#define BIT_NET_CONNECTED (1 << 0)
#define BIT_SENSOR_READY (1 << 1)
void task_network(void *pvParam)
{
vTaskDelay(2000 / portTICK_PERIOD_MS);
printf("任务1 网络连接成功\n");
xEventGroupSetBits(event_group, BIT_NET_CONNECTED);
vTaskDelete(NULL);
}
void task_sensor(void *pvParam)
{
vTaskDelay(3000 / portTICK_PERIOD_MS);
printf("任务2 传感器初始化完成\n");
xEventGroupSetBits(event_group, BIT_SENSOR_READY);
vTaskDelete(NULL);
}
void task_main(void *pvParam)
{
EventBits_t bits = xEventGroupWaitBits(
event_group,
BIT_NET_CONNECTED | BIT_SENSOR_READY,
pdTRUE, // 清除事件位
pdTRUE, // 等待所有位
portMAX_DELAY
);
printf("所有条件就绪,开始执行主任务\n");
vTaskDelete(NULL);
}
//---------------------------------------------------------
QueueHandle_t data_queue;
void task_producer(void *pvParam)
{
int value = 0;
while (1)
{
printf("Producing: %d\n", value);
xQueueSend(data_queue, &value, portMAX_DELAY); // 发送数据
value++;
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
void task_consumer(void *pvParam)
{
int received;
while (1)
{
if( xQueueReceive(data_queue, &received, pdMS_TO_TICKS(2000)) )
{
printf("Consumed: %d\n", received);
}
else
{
printf("队列超时\n");
}
}
}
void app_main()
{
vTaskDelay(pdMS_TO_TICKS(20000));
bin_sem = xSemaphoreCreateBinary();
xTaskCreate(task_sender, "sender", 2048, NULL, 5, NULL);
xTaskCreate(task_receiver, "receiver", 2048, NULL, 5, NULL);
mutex = xSemaphoreCreateMutex();
xTaskCreate(task_increment, "task1", 2048, (void*)1, 5, NULL);
xTaskCreate(task_increment, "task2", 2048, (void*)2, 5, NULL);
event_group = xEventGroupCreate();
xTaskCreate(task_network, "network", 2048, NULL, 5, NULL);
xTaskCreate(task_sensor, "sensor", 2048, NULL, 5, NULL);
xTaskCreate(task_main, "main_task", 2048, NULL, 5, NULL);
data_queue = xQueueCreate(5, sizeof(int)); // 队列长度5,存储整数
xTaskCreate(task_producer, "producer", 2048, NULL, 5, NULL);
xTaskCreate(task_consumer, "consumer", 2048, NULL, 5, NULL);
while(1)
{
vTaskDelay(pdMS_TO_TICKS(20000));
}
}
代码关键说明
- 二值信号量:
xSemaphoreCreateBinary() 创建信号量,xSemaphoreGive() 释放,xSemaphoreTake() 获取(最大5S等待)
。 - 互斥量:
xSemaphoreCreateMutex() 创建互斥量,xSemaphoreTake() 锁定资源,xSemaphoreGive() 解锁
。 - 事件组:
xEventGroupCreate() 创建事件组,xEventGroupSetBits() 设置事件位,xEventGroupWaitBits() 等待事件位(支持 AND/OR 逻辑)
。 - 队列:
xQueueCreate() 创建队列(指定深度和数据类型),xQueueSend() 发送数据,xQueueReceive() 接收数据(最大2S等待)
。 参数是 portMAX_DELAY ,则表示永久等待
。
总结
同步方法 | 适用场景 | 关键优势 |
---|---|---|
信号量 | 事件通知、资源计数 | 轻量级,支持二进制和计数模式 |
互斥锁 | 保护共享资源 | 防止数据竞争,支持优先级继承 |
队列 | 任务间数据传输 | 结构化数据传递,支持超时机制 |
事件组 | 多事件协同触发 | 高效等待多个条件,位操作灵活 |
注意:
- 避免死锁:确保获取锁/信号量的顺序一致。
- 超时处理:为
xSemaphoreTake
或xQueueReceive
设置合理超时时间。 - 优先级反转:使用互斥锁时,启用优先级继承(ESP-IDF 默认启用)。