前言
本篇文章来为大家讲解信号量的具体使用。
一、使用二值信号量完成同步
下面先举一个代码示例:
创建两个优先级相同的任务,这两个任务同时访问一个串口资源:
void Task1Function(void * param) { int i; while (1) { printf("Task1\r\n"); } } void Task2Function(void * param) { while (1) { printf("Task2\r\n"); } } xTaskCreate(Task1Function, "Task1", 100, NULL, 1, NULL); xTaskCreate(Task2Function, "Task2", 100, NULL, 1, NULL);
运行结果:
通过运行结果可以看出两个任务的打印信息交错在了一起,这就是同时访问共享资源带来的问题,这里我们可以使用二值信号量来解决这个问题。
void Task1Function(void * param) { while (1) { if (xSemaphoreTake(xSem, portMAX_DELAY) == pdTRUE) { printf("Task1\r\n"); xSemaphoreGive(xSem); vTaskDelay(1); } else { printf("Task1 xSemaphoreTake is err\r\n"); } } } void Task2Function(void * param) { while (1) { if (xSemaphoreTake(xSem, portMAX_DELAY) == pdTRUE) { printf("Task2\r\n"); xSemaphoreGive(xSem); vTaskDelay(1); } else { printf("Task2 xSemaphoreTake is err\r\n"); } } } xSem = xSemaphoreCreateBinary(); xSemaphoreGive(xSem); xTaskCreate(Task1Function, "Task1", 100, NULL, 1, NULL); xTaskCreate(Task2Function, "Task2", 100, NULL, 1, NULL);
运行效果:
通过现象可以得知,使用信号量可以完成同步访问共享资源。
二、使用计数型信号量
#define BUFFER_SIZE 5 #define NUM_PRODUCERS 2 #define NUM_CONSUMERS 2 SemaphoreHandle_t bufferMutex; SemaphoreHandle_t itemsCount; int buffer[BUFFER_SIZE]; int itemCount = 0; void producerTask(void *param) { int producerId = (int)param; while (1) { // 产生一个随机的数据 int data = rand() % 100; // 尝试获取 itemsCount 计数型信号量,表示可用的缓冲区数量 xSemaphoreTake(itemsCount, portMAX_DELAY); // 获取 bufferMutex 二值型信号量,保护缓冲区的访问 xSemaphoreTake(bufferMutex, portMAX_DELAY); // 将数据放入缓冲区 buffer[itemCount] = data; itemCount++; printf("Producer %d - Produced: %d, Total items: %d\n", producerId, data, itemCount); // 释放 bufferMutex 二值型信号量 xSemaphoreGive(bufferMutex); // 通知消费者有新的数据可用 xSemaphoreGive(itemsCount); // 延时一段时间 vTaskDelay(1000 / portTICK_PERIOD_MS); } } void consumerTask(void *param) { int consumerId = (int)param; while (1) { // 尝试获取 itemsCount 计数型信号量,表示可用的缓冲区数量 xSemaphoreTake(itemsCount, portMAX_DELAY); // 获取 bufferMutex 二值型信号量,保护缓冲区的访问 xSemaphoreTake(bufferMutex, portMAX_DELAY); // 从缓冲区获取数据 int data = buffer[itemCount - 1]; itemCount--; printf("Consumer %d - Consumed: %d, Total items: %d\n", consumerId, data, itemCount); // 释放 bufferMutex 二值型信号量 xSemaphoreGive(bufferMutex); // 通知生产者有一个额外的缓冲区可用 xSemaphoreGive(itemsCount); // 延时一段时间 vTaskDelay(2000 / portTICK_PERIOD_MS); } } int main() { // 创建 bufferMutex 二值型信号量 bufferMutex = xSemaphoreCreateMutex(); // 创建 itemsCount 计数型信号量,初始值为 BUFFER_SIZE itemsCount = xSemaphoreCreateCounting(BUFFER_SIZE, BUFFER_SIZE); // 创建生产者任务 for (int i = 0; i < NUM_PRODUCERS; i++) { xTaskCreate(producerTask, "Producer", configMINIMAL_STACK_SIZE, (void *)i, tskIDLE_PRIORITY + 1, NULL); } // 创建消费者任务 for (int i = 0; i < NUM_CONSUMERS; i++) { xTaskCreate(consumerTask, "Consumer", configMINIMAL_STACK_SIZE, (void *)i, tskIDLE_PRIORITY + 2, NULL); } // 启动调度器 vTaskStartScheduler(); // 如果一切正常,下面的代码不应该执行到 while (1) { } return 0; }
使用二值型信号量 bufferMutex 来保护对缓冲区的访问,以防止多个任务同时访问引发竞争条件。使用计数型信号量 itemsCount 表示可用的缓冲区数量。当生产者将数据放入缓冲区时,会获取 itemsCount 信号量,并在获取成功后释放之前的信号量,从而告知消费者有新的数据可用。相反地,当消费者从缓冲区中取出数据时,会获取 itemsCount 信号量,并在获取成功后释放之前的信号量,从而告知生产者有一个额外的缓冲区可用。
总结
本篇文章就讲解到这里,大家多做实验多巩固复习。