本篇文章来为大家讲解信号量的具体使用。
下面先举一个代码示例:
创建两个优先级相同的任务,这两个任务同时访问一个串口资源:
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 信号量,并在获取成功后释放之前的信号量,从而告知生产者有一个额外的缓冲区可用。
本篇文章就讲解到这里,大家多做实验多巩固复习。