同步互斥与通信

目录

一、同步与互斥的概念

二、同步与互斥并不简单

三、各类方法的对比


一、同步与互斥的概念

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

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

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

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

再举一个例子。在团队活动里,同事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;//计算标志位置1
	time = 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.mfbz.cn/a/770854.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

nginx.conf配置参数解析

nginx配置文件解析 /usr/local/nginx/conf vim /etc/security/limits.conf #配置生效只能重新启动* soft nproc 65535 #能打开的进程最大数是软限制655335,65535是最大值 * hard nproc 65535 * soft nofile 65535 # 进程打开文件数的最大值65535 * hard nof…

最新美联储会议纪要:通胀降温,但不急于降息!

KlipC报道&#xff1a;当地时间周三&#xff0c;美联储公布了6月货币政策会议纪要。纪要显示&#xff0c;数据表明有通胀放缓的迹象&#xff0c;但如果降息需要更多的证据。此外&#xff0c;多位与会者表示&#xff0c;货币政策应随时准备应对意外的经济疲软。 会议纪要显示&a…

python-字典

为什么需要字典 字典的定义 字典数据的获取 字典的嵌套 嵌套字典的内容获取 字典的注意事项&#xff1a; 字典的常用操作 新增元素 更新元素 删除元素 清空字典 汇总 字典的特点

收银系统源码-收银台营销功能-定时折扣

1. 功能描述 定时折扣&#xff1a;在特定的时间段&#xff0c;将商品以打折的方式在收银台售卖&#xff0c;例如生鲜行业&#xff0c;由于生鲜是易耗品&#xff0c;很多门店晚上都会通过打折的方式进行促销&#xff1b; 2.适用场景 新门店开业、门店周年庆、节假日等特定时间…

深度分析和对比本地大语言模型Ollama和LocalAI

前言 在充满活力的人工智能&#xff08;AI&#xff09;世界中&#xff0c;开源工具已成为开发人员和组织利用LLM&#xff08;大型语言模型&#xff09;力量的重要资源。这些工具通过提供对高级LLM模型的访问权限&#xff0c;使各种用户能够构建创新和前沿的解决方案。在众多可…

springboot @configuration注解的配置, @bean注解方法a, 在@bean注解 getb(){}需要注入a

深度解析Configuration注解 /*** General purpose AOP callback. Used when the target is dynamic or when the* proxy is not frozen.*/private static class DynamicAdvisedInterceptor implements MethodInterceptor, Serializable {private final AdvisedSupport advised;…

实验九 存储过程和触发器

题目 创建并执行一个无参数的存储过程proc_product1&#xff0c;通过该存储过程可以查询商品类别名称为“笔记本电脑”的商品的详细信息&#xff1a;包括商品编号、商品名称、品牌、库存量、单价和上架时间信息 2、创建并执行一个带输入参数的存储过程proc_product2&#xff…

Rethinking Federated Learning with Domain Shift: A Prototype View

CVPR2023,针对分布式数据来自不同的域时,私有模型在其他域上表现出退化性能(具有域转移)的问题。提出用于域转移下联邦学习的联邦原型学习(FPL)。核心思想是构建集群原型和无偏原型,提供富有成效的领域知识和公平的收敛目标。将样本嵌入拉近到属于相同语义的集群原型,而…

【前端】IntersectionObserver 实现图片懒加载和无限滚动

【前端】IntersectionObserver 实现图片懒加载和无限滚动 在前端开发中&#xff0c;性能优化是一个重要的考量因素。随着现代网页和应用的复杂性增加&#xff0c;确保页面快速加载和流畅运行变得越来越重要。本文将介绍一种强大的工具——IntersectionObserver API&#xff0c…

智胜未来:AI如何重塑SaaS用户增长战略

在当今这个数字化时代&#xff0c;SaaS&#xff08;软件即服务&#xff09;已成为企业运营的重要支柱&#xff0c;而人工智能&#xff08;AI&#xff09;技术的迅猛发展&#xff0c;正以前所未有的方式重塑着SaaS行业的面貌&#xff0c;特别是对其用户增长战略产生了深远影响。…

每日一题——Python实现PAT乙级1005 继续(3n+1)猜想(举一反三+思想解读+逐步优化)五千字好文

一个认为一切根源都是“自己不够强”的INTJ 个人主页&#xff1a;用哲学编程-CSDN博客专栏&#xff1a;每日一题——举一反三Python编程学习Python内置函数 Python-3.12.0文档解读 目录 我的写法 代码逻辑概述 时间复杂度分析 空间复杂度分析 总结 我要更强 代码优化点…

Openwrt路由器部分ipv6公网地址无法访问的问题

路由器是Openwrt&#xff0c;终端访问ipv6地址经常有些能访问&#xff0c;有些不能访问&#xff0c;一开始以为是运营商问题&#xff0c;后面ssh到openwrt发现所有访问都正常。 查阅资料后才知道是MTU设置问题&#xff0c;Openwrt 默认MTU是1492&#xff0c;使用IPV6应减少60个…

Word文档中公式的常用操作

一、参考资料 二、常用操作 插入公式 Alt 多行公式 Shift Enter 多行公式对齐 WORD Tips: 多行公式编辑及对齐 word自带公式等号对齐&#xff08;可任意符号处对齐&#xff09; 多行公式按照 为基准对齐。 拖动鼠标选中整个公式点击右键&#xff0c;选择【对齐点(…

[激光原理与应用-97]:激光焊接焊中检测系统系列介绍 - 1 - 什么是焊接以及传统的焊接方法

目录 一、什么是焊接 1.1 概述 1.2 基本原理 二、传统的焊接技术与方法 2.1 手工电弧焊&#xff1a; 1、定义与原理 2、特点 3、焊条类型 4、应用领域 5、安全注意事项 2.2 气体保护焊&#xff1a; 1、原理与特点 2、应用领域 3、气体选择 4、注意事项 2.3 电阻…

C++ 文达校内党员管理系统-计算机毕业设计源码20855

目 录 摘要 1 绪论 1.1研究背景与意义 1.2国内外研究现状 1.3论文结构与章节安排 2 文达校内党员管理系统系统分析 2.1 可行性分析 2.2 系统功能分析 2.2.1 功能性分析 2.2.2 非功能性分析 2.3 系统用例分析 2.4 系统流程分析 2.4.1 数据流程 2.5.2 业务流程 2.…

CPU/内存/综合性能评估工具汇总-3:unixbench

目录 一、概括二、UnixBench 一、概括 嵌入式开发中对要设计的产品、立项的项目进行设计时&#xff0c;往往需要对关键芯片进行性能评估&#xff0c;本文主要总结基于linux系统的产品在性能评估时的工具使用总结&#xff0c;在aarch64(arm64平台下测试)&#xff0c;板卡根文件…

前端学习(三)CSS介绍及选择符

##最近在忙期末考试&#xff0c;因此前端笔记的梳理并未及时更新。在学习语言过程中&#xff0c;笔记的梳理对于知识的加深very vital.因此坚持在明天学习新知识前将笔记梳理完整。 主要内容&#xff1a;CSS介绍及选择符 最后更新时间&#xff1a;2024/7/4 目录 内容&#x…

Redis 7.x 系列【15】持久化机制之 RDB

有道无术&#xff0c;术尚可求&#xff0c;有术无道&#xff0c;止于术。 本系列Redis 版本 7.2.5 源码地址&#xff1a;https://gitee.com/pearl-organization/study-redis-demo 文章目录 1. 概述2 执行原理3. 配置项3.1 save3.2 stop-writes-on-bgsave-error3.3 rdbcompress…

HMI 的 UI 风格创造奇迹

HMI 的 UI 风格创造奇迹

关于巴图自动化Profinet协议转Modbus协议网关模块怎么配置IP地址教学

Profinet协议和Modbus协议是工业领域中常用的两种通讯协议&#xff0c;除此以外还有较为常见的&#xff1a;ModbusTCP协议&#xff0c;Profibus协议&#xff0c;Profibus DP协议&#xff0c;EtherCAT协议&#xff0c;EtherNET协议&#xff0c;CAN&#xff0c;CANOPEN等它们在自…