同步互斥与通信

目录

一、同步与互斥的概念

二、同步与互斥并不简单

三、各类方法的对比


一、同步与互斥的概念

一句话理解同步与互斥:我等你用完厕所,我再用厕所。

什么叫同步?就是:哎哎哎,我正在用厕所,你等会。

什么叫互斥?就是:哎哎哎,我正在用厕所,你不能进来。

同步与互斥经常放在一起讲,是因为它们之的关系很大,“互斥”操作可以使用“同步”来 实现。我“等”你用完厕所,我再用厕所。这不就是用“同步”来实现“互斥”吗?

再举一个例子。在团队活动里,同事A先写完报表,经理B才能拿去向领导汇报。

经理B必须等同事A完成报表,AB之间有依赖,B必须放慢脚步,被称为同步。在团队活动中,同 事A已经使用会议室了,经理B也想使用,即使经理B是领导,他也得等着,这就叫互斥。经 理B跟同事A说:你用完会议室就提醒我。这就是使用"同步"来实现"互斥"。

01 void 抢厕所(void)
02 {
03     if (有人在用) 我眯一会;
04     用厕所;
05     喂,醒醒,有人要用厕所吗;
06 }

假设有A、B两人早起抢厕所,A先行一步占用了;B慢了一步,于是就眯一会;当A用完后叫醒B,B也就愉快地上厕所了。

在这个过程中,A、B是互斥地访问“厕所”,“厕所”被称之为临界资源。我们使用了“休眠-唤醒”的同步机制实现了“临界资源”的“互斥访问”。

同一时间只能有一个人使用的资源,被称为临界资源。比如任务A、B都要使用串口来打印,串口就是临界资源。如果A、B同时使用串口,那么打印出来的信息就是A、B混杂, 无法分辨。所以使用串口时,应该是这样:A用完,B再用;B用完,A再用。

二、同步与互斥并不简单

在裸机程序里,可以使用一个全局变量或静态变量实现互斥操作,比如要互斥地使用 LCD,可以使用如下代码:

01 int LCD_PrintString(int x, int y, char *str)
02 {
03     static int bCanUse = 1;
04     if (bCanUse)
05     {
06         bCanUse = 0;
07         /* 使用LCD */
08         bCanUse = 1;
09         return 0;
10     }
11     return -1;
12 }

但是在 RTOS 里,使用上述代码实现互斥操作时,大概率是没问题的,但是无法确保万无一失。

假设如下场景:有两个任务 A、B 都想调用 LCD_PrintString,任务 A 执行到第 4 行代码时发现 bCanUse 为 1,可以进入 if 语句块,它还没执行第 6 句指令就被切换出去了;然后任务 B 也调用 LCD_PrintString,任务 B 执行到第 4 行代码时也发现 bCanUse 为 1,也可以进入 if 语句块使用 LCD。在这种情况下,使用静态变量并不能实现互斥操作

上述例子中,是因为第 4、第 6 两条指令被打断了,那么如下改进:在函数入口处先然让 bCanUse 减一。这能否实现万无一失的互斥操作呢?

01 int LCD_PrintString(int x, int y, char *str)
02 {
03     static int bCanUse = 1;
04     bCanUse--;
05     if (bCanUse == 0)
06     {
07         /* 使用LCD */
08         bCanUse++;
09         return 0;
10     }
11     else
12     {
13         bCanUse++;
14         return -1;
15     }
16 }

把第 4 行的代码使用汇编指令表示如下:

04.1 LDR R0, [bCanUse]     // 读取bCanUse的值,存入寄存器R0

04.2 DEC R0, #1                 // 把R0的值减一

04.3 STR R0, [bCanUse]    // 把R0写入变量bCanUse

假设如下场景:有两个任务 A、B 都想调用 LCD_PrintString,任务 A 执行到第 04.1 行代码时读到的 bCanUse 为 1,存入寄存器 R0 就被切换出去了;然后任务 B 也调用LCD_PrintString,任务 B 执行到第 4 行时发现 bCanUse 为 1 并把它减为 0,执行到第 5 行代码时发现条件成立可以进入 if 语句块使用 LCD,然后任务 B 也被切换出去了;现在任务 A 继续运行第 04.2 行代码时 R0 为 1,运行到第 04.3 行代码时把 bCanUse 设置为 0,后续也能成功进入 if 的语句块。在这种情况下,任务 A、B 都能使用 LCD。

上述方法不能保证万无一失的原因在于:在判断过程中,被打断了。如果能保证这个过程不被打断,就可以了:通过关闭中断来实现。

示例 1 的代码改进如下:在第 5~7 行前关闭中断。

01 int LCD_PrintString(int x, int y, char *str)
02 {
03     static int bCanUse = 1;
04     disable_irq();        //关闭中断
05     if (bCanUse)
06     {
07         bCanUse = 0;
08         enable_irq();    //开启中断
09         /* 使用LCD */
10         bCanUse = 1;
11         return 0;
12     }
13     enable_irq();        //开启中断
14     return -1;
15 }

示例 2 的代码改进如下:在第 5 行前关闭中断。

01 int LCD_PrintString(int x, int y, char *str)
02 {
03     static int bCanUse = 1;
04     disable_irq();
05     bCanUse--;
06     enable_irq();
07     if (bCanUse == 0)
08     {
09         /* 使用LCD */
10         bCanUse++;
11         return 0;
12     }
13     else
14     {
15         disable_irq();
16         bCanUse++;
17         enable_irq();
18         return -1;
19     }
20 }

关闭中断的方法并不是万无一失的:假设现在有任务A和任务B在执行以下函数,在A打印后过了1ms,B被调度,但B只是进行判断,但会一直失败,过了1msA被调度继续打印,打印完后过了1ms轮到B,B也是继续判断然后一直失败,这样子导致的结果是B占用了cpu资源,与同步例子类似,那么此时解决方法是:将B执行时若A已经在打印了,将B设置为阻塞状态,于是A继续打印,等到A打印完毕,将B唤醒,B才能正常打印。

void CalTask(void *params)//计时函数
{uint32_t i = 0;time = system_get_ns();//程序执行到此的时间for(i=0;i<10000000;i++){sum += i;}Cal_end = 1;//计算标志位置1time = system_get_ns() - time;//先计算程序到此的时间 再减去之前的时间 得到for循环中的时间vTaskDelete(NULL);
}void LcdPrintTask(void *params)
{int len;while(1){/* 打印信息 */LCD_PrintString(0,0,"waitting");vTaskDelay(2000);//让此任务进入阻塞状态 等上面任务执行完毕在执行这里 让下面的while死循环不会占用cpu资源 不调用此函数则不会进入阻塞 会一直死等 为同步例子while(Cal_end == 0);//这里是在等待上面的计数完毕 若不使用上一行的vTaskDelay函数 则会在这里死等 占用cpu资源 因为两个任务是交叉执行 若没有上一行的vTaskDelay函数 烧录完发现时间为2s左右 由于两个任务叫交叉执行 若将B任务删除只执行A任务 则时间差不多为一半即1ms 那么此时就可以调用vTaskDelay函数让此任务进入阻塞状态 让task1先执行 执行完毕计时标志位置1 同时vTaskDelay函数计时完毕后进入此时的死循环 此时计时标志位置1直接跳出循环 不会让程序卡在这里占用cpu资源 就可以准确计算出程序执行时间if(flag){flag = 0;LCD_ClearLine(0,0);len = LCD_PrintString(0,0,"Sum:");//这里的返回值是打印任务的长度len += LCD_PrintHex(len,0,sum,1);//将打印名字处后打印":"的长度一起加上 得到的长度是打印完任务名字和":"后的长度LCD_ClearLine(0,2);len = LCD_PrintString(0,2,"Time(ms):");len += LCD_PrintSignedVal(len,2,time/1000000);flag = 1;}vTaskDelete(NULL);}
}/* FreeRTOS.c中创建任务 */
xTaskCreate(CalTask,"taskA",128,NULL,osPriorityNormal,NULL);
xTaskCreate(LcdPrintTask,"taskB",128,&Task2,osPriorityNormal,NULL);

同步例子(任务B没有阻塞)

由结果可知,两个任务程序是交叉进行的,在RTOS中,任务级相同的任务每隔1s交叉运行,所以任务A执行1s(开始计时)后切到任务B,而A没执行完毕 flag 始终为 0,那么任务B就会死等(任务A没执行完毕,仍在计时) flag 为1,当B执行完A继续执行(继续计时)后 flag 为1后进入任务B才能成功计时完毕。可见时间大约为2s,若只有程序A执行,那么时间大约为1s左右。

互斥例子(任务B阻塞)

任务A不断计时,任务A执行完1s后任务B执行,而任务B中调用了阻塞函数,所以继续轮到任务A执行,任务A执行完毕跳转到任务B中B成功打印计时值。(这里的计数值主要来源于任务A,所以若任务A执行完毕,会停止计时,所以B中的阻塞延时(只是2s后延时)对于计时结果无影响,为了能够更好准确的得到计时值)

三、各类方法的对比

能实现同步、互斥的内核方法有:任务通知(task notification)、队列(queue)、事件组 (event group)、信号量(semaphoe)、互斥量(mutex)。

它们都有类似的操作方法:获取/释放、阻塞/唤醒、超时。比如:

  • 任务 A 获取资源,用完后任务 A 释放资源
  • 任务 A 获取不到资源则阻塞,任务 B 释放资源并把任务 A 唤醒
  • 任务 A 获取不到资源则阻塞,并定个闹钟;A 要么超时返回,要么在这段时间内因为任务 B 释放资源而被唤醒。

通过对比的方法来区分:

  • 能否传信息?还是只能传递状态?
  • 为众生(所有任务都可以使用)?只为你(只能指定任务使用)?
  • 我生产,你们消费?
  • 我上锁,只能由我开锁
内核对
生产
消费
数据/状态
说明
队列
ALL
ALL
数据:若干个数据
谁都可以往队列里扔数据,
谁都可以从队列里读数据
用来传递数据,
发送者、接收者无限制,
一个数据只能唤醒一 个接收者
事件组
ALL
ALL
多个位:或、与
谁都可以设置(生产)多个 位,
谁都可以等待某个位、若 干个位
用来传递事件,

可以是 N 个事件,

发送者、接受者无限制,
可以唤醒多个接收 者:像广播
信号量
ALL
ALL
数量:0~n
谁都可以增加一个数量,
谁都可消耗一个数量
用来维持资源的个数,
生产者、消费者无限
制,
1 个资源只能唤醒 1
个接收者
任务通知
ALL
只有我
数据、状态都可以传输,
使用任务通知时,
必须指定接受者
N 对 1 的关系:
发送者无限制,
接收者只能是这个任
互斥量
A 上锁
只能 A 开锁
位:0、1
我上锁:1 变为 0,
只能由我开锁:0 变为 1
就像一个空厕所,
谁使用谁上锁,
也只能由他开锁

使用图形对比如下:

  • 队列:
    • 里面可以放任意数据,可以放多个数据
    • 任务、ISR 都可以放入数据;任务、ISR 都可以从中读出数据
  • 事件组:
    • 一个事件用一 bit 表示,1 表示事件发生了,0 表示事件没发生
    • 可以用来表示事件、事件的组合发生了,不能传递数据
    • 有广播效果:事件或事件的组合发生了,等待它的多个任务都会被唤醒
  • 信号量:
    • 核心是"计数值"
    • 任务、ISR 释放信号量时让计数值加 1
    • 任务、ISR 获得信号量时,让计数值减 1
  • 任务通知:
    • 核心是任务的 TCB 里的数值
    • 会被覆盖
    • 发通知给谁?必须指定接收任务
    • 只能由接收任务本身获取该通知
  • 互斥量:
    • 数值只有 0 或 1
    • 谁获得互斥量,就必须由谁释放同一个互斥量

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/38610.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【实战场景】记一次UAT jvm故障排查经历

【实战场景】记一次UAT jvm故障排查经历 开篇词&#xff1a;干货篇&#xff1a;1.查看系统资源使用情况2.将十进制进程号转成十六进制3.使用jstack工具监视进程的垃圾回收情况4.输出指定线程的堆内存信息5.观察日志6.本地环境复现 总结篇&#xff1a;我是杰叔叔&#xff0c;一名…

线下促销折扣视频介绍

千呼新零售2.0系统是零售行业连锁店一体化收银系统&#xff0c;包括线下收银线上商城连锁店管理ERP管理商品管理供应商管理会员营销等功能为一体&#xff0c;线上线下数据全部打通。 适用于商超、便利店、水果、生鲜、母婴、服装、零食、百货、宠物等连锁店使用。 详细介绍请…

什么是定时器?

前言&#x1f440;~ 上一章我们介绍了阻塞队列以及生产者消息模式&#xff0c;今天我们来讲讲定时器 定时器 标准库中的定时器 schedule()方法 扫描线程 手动实现定时器 任务类 存储任务的数据结构 定时器类 如果各位对文章的内容感兴趣的话&#xff0c;请点点小赞&am…

【Python】列表

目录 一、列表的概念 二、列表的创建 1.变量名 [ ] ..... 2.通过Python内置 的I ist类的构造函数来创建列表 三、操作列表元素的方法 1. 修改 2. 增加元素 3. 删除 4. 其他操作 四、遍历列表 五、列表排序 六、列表切片&#xff08;list slicing&#xff09; 七、…

值得细读的8个视觉大模型生成式预训练方法

作者&#xff1a;vasgaowei&#xff08;已授权原创&#xff09; 编辑: AI生成未来 链接&#xff1a;https://zhuanlan.zhihu.com/p/677794719 大语言模型的进展催生出了ChatGPT这样的应用&#xff0c;让大家对“第四次工业革命”和“AGI”的来临有了一些期待&#xff0c;也作为…

Linux基础指令介绍与详解——原理学习

前言&#xff1a;本节内容标题虽然为指令&#xff0c;但是并不只是讲指令&#xff0c; 更多的是和指令相关的一些原理性的东西。 如果友友只想要查一查某个指令的用法&#xff0c; 很抱歉&#xff0c; 本节不是那种带有字典性质的文章。但是如果友友是想要来学习的&#xff0c;…

深入浅出:npm常用命令详解与实践【保姆级教程】

大家好,我是CodeQi! 在我刚开始学习前端开发的时候,有一件事情让我特别头疼:管理和安装各种各样的依赖包。 那时候,我还不知道 npm 的存在,手动下载和管理这些库简直是噩梦。 后来,我终于接触到了 npm(Node Package Manager),它不仅帮我解决了依赖管理问题,还让我…

Python深度理解系列之【排序算法——冒泡排序】

读者大大们好呀&#xff01;&#xff01;!☀️☀️☀️ &#x1f440;期待大大的关注哦❗️❗️❗️ &#x1f680;欢迎收看我的主页文章➡️木道寻的主页 文章目录 &#x1f525;前言&#x1f680;冒泡排序python实现算法实现图形化算法展示 ⭐️⭐️⭐️总结 &#x1f525;前…

Apache POI、EasyPoi、EasyExcel

目录 ​编辑 &#xff08;一&#xff09;Apache PoI 使用 &#xff08;二&#xff09;EasyPoi使用 &#xff08;三&#xff09;EasyExcel使用 写 读 最简单的读​ 最简单的读的excel示例​ 最简单的读的对象​ &#xff08;一&#xff09;Apache PoI 使用 &#xff08;二&…

Git 安装

目录 Git 安装 Git 安装 在使用 Git 前我们需要先安装 Git。Git 目前支持 Linux/Unix、Solaris、Mac 和 Windows 平台上运行。Git 各平台安装包下载地址为&#xff1a;http://git-scm.com/downloads 在 Linux 平台上安装&#xff08;包管理工具安装&#xff09; 首先&#xff0…

IIS在Windows上的搭建

&#x1f4d1;打牌 &#xff1a; da pai ge的个人主页 &#x1f324;️个人专栏 &#xff1a; da pai ge的博客专栏 ☁️宝剑锋从磨砺出&#xff0c;梅花香自苦寒来 目录 一 概念&#xff1a; 二网络…

深入理解C++中的锁

目录 1.基本互斥锁&#xff08;std::mutex&#xff09; 2.递归互斥锁&#xff08;std::recursive_mutex&#xff09; 3.带超时机制的互斥锁&#xff08;std::timed_mutex&#xff09; 4.带超时机制的递归互斥锁&#xff08;std::recursive_timed_mutex&#xff09; 5.共享…

【python脚本】批量检测sql延时注入

文章目录 前言批量检测sql延时注入工作原理脚本演示 前言 SQL延时注入是一种在Web应用程序中利用SQL注入漏洞的技术&#xff0c;当传统的基于错误信息或数据回显的注入方法不可行时&#xff0c;例如当Web应用进行了安全配置&#xff0c;不显示任何错误信息或敏感数据时&#x…

【TS】TypeScript 原始数据类型深度解析

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 TypeScript 原始数据类型深度解析一、引言二、基础原始数据类型2.1 boolean2.2 …

苍穹外卖--sky-take-out(四)10-12

苍穹外卖--sky-take-out&#xff08;一&#xff09; 苍穹外卖--sky-take-out&#xff08;一&#xff09;-CSDN博客​编辑https://blog.csdn.net/kussm_/article/details/138614737?spm1001.2014.3001.5501https://blog.csdn.net/kussm_/article/details/138614737?spm1001.2…

Unity动画系统(2)

6.1 动画系统基础2-3_哔哩哔哩_bilibili p316 模型添加Animator组件 动画控制器 AnimatorController AnimatorController 可以通过代码控制动画速度 建立动画间的联系 bool值的设定 trigger p318 trigger点击的时候触发&#xff0c;如喊叫&#xff0c;开枪及换子弹等&#x…

错误 [WinError 10013] 以一种访问权限不允许的方式做了一个访问套接字的尝试 python ping

报错提示&#xff1a;错误 [WinError 10013] 以一种访问权限不允许的方式做了一个访问套接字的尝试 用python做了一个批量ping脚本&#xff0c;在windows专业版上没问题&#xff0c;但是到了windows服务器就出现这个报错 解决方法&#xff1a;右键 管理员身份运行 这个脚本 …

sql拉链表

1、定义&#xff1a;维护历史状态以及最新数据的一种表 2、使用场景 1、有一些表的数据量很大&#xff0c;比如一张用户表&#xff0c;大约1亿条记录&#xff0c;50个字段&#xff0c;这种表 2.表中的部分字段会被update更新操作&#xff0c;如用户联系方式&#xff0c;产品的…

在 WebGPU 与 Vulkan 之间做出正确的选择(Making the Right Choice between WebGPU vs Vulkan)

在 WebGPU 与 Vulkan 之间做出正确的选择&#xff08;Making the Right Choice between WebGPU vs Vulkan&#xff09; WebGPU 和 Vulkan 之间的主要区别WebGPU 是什么&#xff1f;它适合谁使用&#xff1f;Vulkan 是什么&#xff1f;它适合谁使用&#xff1f;WebGPU 和 Vulkan…

修改CentOS7 yum源

修改CentOS默认yum源为阿里镜像源 备份系统自带yum源配置文件 mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup 下载ailiyun的yum源配置文件 CentOS7 yum源如下&#xff1a; wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun…