当前位置: 首页 > article >正文

04:同步与互斥

同步与互斥

  • 1、同步
  • 2、互斥
  • 3、队列
    • 3.1、使用队列改进同步代码
    • 3.2、使用队列改进互斥代码

1、同步

   何为同步?在团队活动里,同事A先写完报表,经理B才能拿去向领导汇报。经理B必须等同事A完成报表,AB之间有依赖,B必须放慢脚,被称为同步。

  例如有2个任务Task1和Task2。Task1是将获取温湿度传感器的值,Task2是将Task1获取到的数据通过OLED显示出来。即Task2的执行是依赖Task1的执行情况的,Task2必须等待Task1执行完毕,才能去执行,这种情况被称为同步。

同步实验
Task1:实现1+2+3+…100,
Task2:实现将Task1执行完成的结果打印出来
①FreeRTOSConfig.h文件的代码如下:

#define configTICK_RATE_HZ			( ( TickType_t ) 1000 )//1ms

②freertos_demo.c文件的代码如下:

#include "freertos_demo.h"

void Task1(void);
void Task2(void);
/**
 * 入口函数:创建启动任务,启动调度器
 */
TaskHandle_t Task1_Handler,Task2_Handler;//任务控制块指针
void FreeRTOS_Start(void)
{
    /* 进入临界区:防止中断打断 */
    vPortEnterCritical();
    
    /* 1、创建任务1 */
    xTaskCreate((TaskFunction_t)Task1,"Task1",128,NULL,2,&Task1_Handler);//优先级为2

    /* 2、创建任务2 */
    xTaskCreate((TaskFunction_t)Task2,"Task2",128,NULL,1,&Task2_Handler);//优先级为1
    
    /* 退出临界区 */
    vPortExitCritical();
    
    /* 2、启动调度器 */
    vTaskStartScheduler();//启动了任务调度器,任务就开始运行了,且创建空闲任务。
}

/**
 * 任务1函数:实现1+2+3+....100
 */
uint16_t Number = 0;    //存储Task1执行后的结果
//uint8_t Flag = 0;       //标志位
void Task1(void)
{
    while (1)
    {
        uint16_t sum = 0;
        for(uint16_t i = 1;i <= 100; i++)
        {
            sum += i;
        }
        Number = sum;//将Task执行的结果給全局变量Number
        vTaskDelay(10);
    }
}

/**
 * 任务2函数:将Task1执行完成的结果打印出来
 */
void Task2(void)
{
    while (1)
    {
        printf("Number = %d\r\n",Number);
        HAL_Delay(1000);//每隔1s打印一次
    }
}

在这里插入图片描述上面的Task1的优先级高于Task2,所以Task1执行完后才能执行Task2,不易体现出同步过程。将Task1的优先级改与Task2相同。则代码如下:
②freertos_demo.c文件的代码如下:

#include "freertos_demo.h"

void Task1(void);
void Task2(void);
/**
 * 入口函数:创建启动任务,启动调度器
 */
TaskHandle_t Task1_Handler,Task2_Handler;//任务控制块指针
void FreeRTOS_Start(void)
{
    /* 进入临界区:防止中断打断 */
    vPortEnterCritical();
    
    /* 1、创建任务1 */
    xTaskCreate((TaskFunction_t)Task1,"Task1",128,NULL,1,&Task1_Handler);//优先级为1

    /* 2、创建任务2 */
    xTaskCreate((TaskFunction_t)Task2,"Task2",128,NULL,1,&Task2_Handler);//优先级为1
    
    /* 退出临界区 */
    vPortExitCritical();
    
    /* 2、启动调度器 */
    vTaskStartScheduler();//启动了任务调度器,任务就开始运行了,且创建空闲任务。
}

/**
 * 任务1函数:实现1+2+3+....100
 */
uint16_t Number = 0;    //存储Task1执行后的结果
uint8_t Flag = 0;       //标志位
void Task1(void)
{
    while (1)
    {
        uint16_t sum = 0;
        for(uint16_t i = 1;i <= 100; i++)
        {
            sum += i;
        }
        Number = sum;   //将Task执行的结果給全局变量Number
        Flag = 1;       //标志位置1
    }
}

/**
 * 任务2函数:将Task1执行完成的结果打印出来
 */
void Task2(void)
{
    while (1)
    {
        if(Flag == 1)
        {
            printf("Number = %d\r\n",Number);
            HAL_Delay(1000);//每隔1s打印依次
        }
    }
}

在这里插入图片描述
综上:任务之间的数据传递需要使用全局变量。Task1和Task2的优先级相同,即想要Task1执行完成得出结果后再去执行Task2,则需要一个标志位Flag。当Task1执行完成后将Flag置为1。才去执行Task2,将Task1执行的结果打印出来

上面的方法使用了全局变量,通过全局变量来抑制了Task2中if后续代码的执行。
但是,上面的同步方法存在着一个很大的问题,就是依然会去执行Task2任务,Task2会去和Task1竞争CPU资源,造成了CPU资源的浪费。如下图所示:会给Task1浪费掉1ms的时间。

在这里插入图片描述那有没有更好的方法,既可以进行任务间的数据传输,又可以避免CPU资源的浪费喃?

2、互斥

   何为互斥?例如有2个任务Task1和Task2。Task1使用串口UART1进行打印数据 “欢迎学习嵌入式”,而Task2也使用UART1进行打印数据 “你好世界”。不同的任务使用同一个资源时,这种情况被称为互斥。

互斥实验
Task1:使用UART1打印“欢迎学习嵌入式”,
Task2:使用UART1打印“你好世界”
①FreeRTOSConfig.h文件的代码如下:

#define configTICK_RATE_HZ			( ( TickType_t ) 1000 )//1ms

②freertos_demo.c文件的代码如下:

#include "freertos_demo.h"

void Task(void * pvParameters);
/**
 * 入口函数:创建启动任务,启动调度器
 */
TaskHandle_t Task1_Handler,Task2_Handler;//任务控制块指针
void FreeRTOS_Start(void)
{
    /* 进入临界区:防止中断打断 */
    vPortEnterCritical();
    
    /* 1、创建任务1 */
    xTaskCreate((TaskFunction_t)Task,"Task1",128,"欢迎学习嵌入式",1,&Task1_Handler);//优先级为1

    /* 2、创建任务2 */
    xTaskCreate((TaskFunction_t)Task,"Task2",128,"你好世界",1,&Task2_Handler);//优先级为1
    
    /* 退出临界区 */
    vPortExitCritical();
    
    /* 2、启动调度器 */
    vTaskStartScheduler();//启动了任务调度器,任务就开始运行了,且创建空闲任务。
}

/**
 * 任务函数:使用UART1打印
 */
void Task(void * pvParameters)
{
    while (1)
    {
    	printf("%s\r\n",(char *)pvParameters);
    }
}


在这里插入图片描述综上:2个任务都需要使用UART1进行打印输出,当Task1还没有打印完,Task2使用串口打印,最终造成打印出来的数据是混乱的,所以Task1和Task2存在互斥的现象。
所以我们通过一个全局变量来标志UART1的使用情况。当UART1正在使用时,让Flag置1,只有Flag为0时,才能够去使用UART1。

/**
 * 任务函数:使用UART1打印
 */
volatile uint8_t Flag = 0;
void Task(void * pvParameters)
{
    while (1)
    {
        if(Flag == 0)
        {
            Flag = 1;
            printf("%s\r\n",(char *)pvParameters);
            Flag = 0;
        }
    }
}

在这里插入图片描述综上:Task1并没有打印,这种情况是Task2执行1ms的时间,代码执行了1轮后,执行到 Flag = 1;然后发生调度,去执行Task1。而此时的Task1无论如何都不会发生打印。所以在Task2执行打印完后添加vTaskDelay()

/**
 * 任务函数:使用UART1打印
 */
volatile uint8_t Flag = 0;
void Task(void * pvParameters)
{
    while (1)
    {
        if(Flag == 0)
        {
            Flag = 1;
            printf("%s\r\n",(char *)pvParameters);
            Flag = 0;
            vTaskDelay(1);
        }
    }
}

在这里插入图片描述
使用全局变量来解决互斥情况依然存在BUG。

3、队列

   通过上面的实验,可以使用全局变量来解决同步和互斥的情况,但是依然存在或多或少的问题。所以为了解决这种问题,在FreeRTOS可以使用队列来解决。

  • FreeRTOS中FIFO是环形队列,采用先进先出的原则
  • 队列可以包括若干个数据,队列中有若干个数据,且每个数据的大小固定
  • 创建队列时要指定长度,数据的大小
    在这里插入图片描述

创建队列的代码

/**
 * 函数:动态方式创建队列
 * uxQueueLength:队列长度
 * uxItemSize:数据大小
 * 返回值:QueueHandle_t = (Queue_t *)变量
 */
xQueueCreate(uxQueueLength, uxItemSize)

/**
 * 函数:静态方式创建队列
 * uxQueueLength:队列长度
 * uxItemSize:数据大小
 * pucQueueStorage:静态分配的数组指针
 * pxQueueBuffer:静态队列控制块的指针
 */
xQueueCreateStatic(uxQueueLength, uxItemSize, pucQueueStorage, pxQueueBuffer)
QueueHandle_t Queue1;
Queue1 = xQueueCreate(5, sizeof(int));//创建长度为5的环形Buffer,存储1个数据大小为4个字节

在这里插入图片描述

向队列写入数据的代码

/**
 * 函数:往队列的尾部写入消息
 * xQueue:QueueHandle_t变量,写入哪个队列
 * pvItemToQueue:需要被写入数据的空间指针
 * xTicksToWait:阻塞时间:若队列满了,则任务阻塞
 */
xQueueSend(xQueue, pvItemToQueue, xTicksToWait)
/**
 * 函数:同xQueueSend()
 */
xQueueSendToBack(xQueue, pvItemToQueue, xTicksToWait)
/**
 * 函数:往队列的头部写入消息
 * xQueue:QueueHandle_t变量,写入哪个队列
 * pvItemToQueue:需要被写入数据的空间指针
 * xTicksToWait:阻塞时间:若队列满了,则任务阻塞
 */ 	
xQueueSendToFront(xQueue, pvItemToQueue, xTicksToWait) 	
/**
 * 函数:覆写队列消息(只用于队列长度为 1 的情况)
 * xQueue:QueueHandle_t变量,写入哪个队列
 * pvItemToQueue:存储读取数据的空间指针
 */
xQueueOverwrite(xQueue, pvItemToQueue) 	

/************下面的在中断里面写队列********/
/**
 * 函数:在中断中往队列的尾部写入消息
 * xQueue:QueueHandle_t变量,写入哪个队列
 * pvItemToQueue:需要被写入数据的空间指针
 */
xQueueSendFromISR(xQueue, pvItemToQueue, pxHigherPriorityTaskWoken) 	
xQueueSendToBackFromISR()xQueueSendFromISR()
xQueueSendToFrontFromISR() 	在中断中往队列的头部写入消息
xQueueOverwriteFromISR() 	在中断中覆写队列消息(只用于队列长度为 1 的情况)
uint8_t Data[3] = {1,2,3};
for(uint8_t i = 0;i<3;i++)
{
	xQueueSend(Queue1 , &Data, portMAX_DELAY);//往队列的尾部写入数据
}

在这里插入图片描述

uint8_t Data[3] = {1,2,3};
for(uint8_t i = 0;i<3;i++)
{
	xQueueSendToFront(Queue1 , &Data, portMAX_DELAY);//往队列的头部写入数据
}

在这里插入图片描述
若队列满,则向队列中写入数据的任务将会被阻塞,当队列中有空间可以写入数据时,任务会立刻被唤醒,并继续执行写入操作。

从队列读取数据的代码

/**
 * 函数:从队列头部读取消息,并删除消息
 */
xQueueReceive(xQueue, pvBuffer, xTicksToWait)

/**
 * 函数:从队列头部读取消息
 */
xQueuePeek(xQueue, pvBuffer, xTicksToWait) 	

/************下面的在中断里面写队列********/
xQueueReceiveFromISR() 	在中断中从队列头部读取消息,并删除消息
xQueuePeekFromISR() 	在中断中从队列头部读取消息
uint8_t Buff[3];
for(uint8_t i = 0; i<3; i++)
{
	xQueueReceive(Queue1 , &Buff[i], portMAX_DELAY);//将数据读取到Buff里面
}

在这里插入图片描述
在这里插入图片描述
若队列空,则从队列中读取数据的任务将会被阻塞

3.1、使用队列改进同步代码

①freertos_demo.c文件的代码如下:

#include "freertos_demo.h"

void Task1(void * pvParameters);
void Task2(void * pvParameters);
/**
 * 入口函数:创建启动任务,启动调度器
 */
TaskHandle_t Task1_Handler,Task2_Handler;   //任务控制块指针
QueueHandle_t Queue1;                       //创建队列句柄
void FreeRTOS_Start(void)
{
    Queue1 = xQueueCreate(1,sizeof(uint16_t));  //创建一个队列,长度为1.保存uint16_t大小的数据

    /* 进入临界区:防止中断打断 */
    vPortEnterCritical();
    
    /* 1、创建任务1 */
    xTaskCreate((TaskFunction_t)Task1,"Task1",128,NULL,1,&Task1_Handler);//优先级为1

    /* 2、创建任务2 */
    xTaskCreate((TaskFunction_t)Task2,"Task2",128,NULL,1,&Task2_Handler);//优先级为1
    
    /* 退出临界区 */
    vPortExitCritical();
    
    /* 2、启动调度器 */
    vTaskStartScheduler();//启动了任务调度器,任务就开始运行了,且创建空闲任务。
}

/**
 * 任务1函数:实现1+2+3+....100
 */
void Task1(void * pvParameters)
{
    while (1)
    {
        uint16_t sum = 0;
        for(uint16_t i = 1;i <= 100; i++)
        {
            sum += i;
        }
        xQueueSend(Queue1, &sum, portMAX_DELAY);//将计算好的数据写入到队列中
    }
}

/**
 * 任务2函数:将Task1执行完成的结果打印出来
 */
void Task2(void * pvParameters)
{
    uint16_t Buff = 0;
    while (1)
    {
        xQueueReceive(Queue1 , &Buff, portMAX_DELAY);//将数据读取到Buff里面
        printf("Buff = %d\r\n",Buff);
        HAL_Delay(1000);    //每隔1s打印依次
    }
}

在这里插入图片描述

在这里插入图片描述
综上:使用队列来取代全局变量,既能进行任务间的数据的传递,又降低CPU的消耗。当任务2进行执行时,发生队列里面为空,那么就会进行阻塞,不参与调度。当Task1将数据放入队列里面,队列里面不为空时,将Task2进行唤醒

3.2、使用队列改进互斥代码

①freertos_demo.c文件的代码如下:

#include "freertos_demo.h"

void Task(void * pvParameters);
/**
 * 入口函数:创建启动任务,启动调度器
 */
TaskHandle_t Task1_Handler,Task2_Handler;//任务控制块指针
QueueHandle_t Queue1;
void FreeRTOS_Start(void)
{
    Queue1 = xQueueCreate(1, sizeof(uint8_t));//创建一个队列,长度为1.保存uint8_t大小的数据
    /* 进入临界区:防止中断打断 */
    vPortEnterCritical();
    
    /* 1、创建任务1 */
    xTaskCreate((TaskFunction_t)Task,"Task1",128,"欢迎学习嵌入式",1,&Task1_Handler);//优先级为1

    /* 2、创建任务2 */
    xTaskCreate((TaskFunction_t)Task,"Task2",128,"你好世界",1,&Task2_Handler);//优先级为1
    
    /* 退出临界区 */
    vPortExitCritical();
    
    /* 2、启动调度器 */
    vTaskStartScheduler();//启动了任务调度器,任务就开始运行了,且创建空闲任务。
}

/**
 * 任务函数:使用UART1打印
 */
void Task(void * pvParameters)
{
    uint8_t Data = 1;
    uint8_t Buff = 0;
    while (1)
    {
        xQueueSend(Queue1,&Data,portMAX_DELAY);//向队列里面写入数据
    	printf("%s\r\n",(char *)pvParameters);
        xQueueReceive(Queue1,&Buff,portMAX_DELAY);//从队列里面读取数据
        vTaskDelay(1000);
    }
}

在这里插入图片描述
综上:使用队列来取代全局变量,当Task2使用UART1时,给队列中写入数据,使队列满。若在打印过程中发生了调度去执行Task1,Task1也去个队列写入数据,但是发现队列满了,所以Task1将会被阻塞。然后去执行Task2。当Task2执行完后,读取队列的数据,使队列空,当队列空时,会使Task1恢复到就绪态,准备执行。


http://www.kler.cn/a/500496.html

相关文章:

  • Objective-C语言的语法
  • zookeeper监听机制(Watcher机制)
  • 【教程】Unity 本地化多语种 | Localization 工具组
  • 【PDF转Word】 PDF在线转word文档 好用!优质网站资源推荐
  • Java 实现 Elasticsearch 查询当前索引全部数据
  • OOM排查思路
  • 蓝桥杯历届真题 #食堂(C++,Java)
  • 探讨人工智能机器人学之路径规划与导航:A*算法、Dijkstra算法等路径规划方法
  • 《零基础Go语言算法实战》【题目 2-12】Go 语言接口的工作原理
  • 冒泡排序基础与实现
  • 微服务的配置共享
  • C# OpenCV机器视觉:波形相似度
  • 深入解析 Spring AI 系列:剖析OpenAI接口接入组件
  • 3 前端: Web开发相关概念 、HTML语法、CSS语法
  • 解锁人工智能的核心:人工神经网络全面解析
  • 计算机网络——网络层-IP地址
  • 初学stm32 --- ADC模拟/数字转换器工作原理
  • ubuntu设置开机无需输入密码自启动todesk,内网穿透natapp
  • 【芯片封测学习专栏 -- 什么是 Chiplet 技术】
  • 数据结构与算--堆实现线段树
  • 专题 - STM32
  • AI在零售行业中的应用:提升顾客体验与运营效率