学习系统编程No.34【线程同步之信号量】
引言:
北京时间:2023/7/29/16:34,一切尽在不言中,前几天追了几部电视剧,看了几部电影,刷了n个视屏,在前天我们才终于从这快乐的日子里恢复过来,然后看了两节课,也就是上篇博客有关生产消费模型相关的知识,从中我发现代码能力极具下降,对于很多C++语法感到非常陌生,哈哈哈!所以学习起来非常的痛苦,不过在我不断的回顾和摸索中,感觉自己得到了升华,对许多语法的掌握好像又上了一层楼,具体和以前比较掌握的怎样我也不清楚,谁叫我们生而为人呢?很多东西学的快忘的也快,现在又不是复习的时候,那么就只能在前进的道路上顺便复习一下,哎!不过好在现在有AI,想要搞懂某个问题需要的成本还是非常低的,也是因为有AI的存在,我对我学过的知识可以说是搞懂的较为彻底,我愿奉之为21世纪最伟大的发明,当然我说的是国外的ChatGPT,反之国内真的惨不忍睹。谈到这里,我真的要吐糟一下通义千问,xxxx,用这东西说实话还不如百度,当然文心一言由于没用过,具体不好说,但是估计也差不多。好了废话不多,为了能够早点更文,正式进入该篇博客的主题,有关信号量相关的知识,当然本质还是多线程相关知识。
回顾生产消费模型
在学习新知识之前,我们先将上篇博客中有关生产消费模型的知识给回顾一下,因为上篇博客由于时间原因,生产消费模型讲解的并不够详细,谁让生产消费模型目前对于我们来说非常重要呢?当然对于以后的我们来说,该知识也是非常重要的,所以这块知识值的我们继续花费时间。
在上篇博客中,我们重点把为什么有生产消费模型和生产消费模型的三种关系,以及生产消费模型的优点给介绍了,并且重点介绍了三种关系之间的关联(互斥、同步),因为这块知识涉及我们在使用代码自己实现该模型时的思路,并且最终依据对应的知识将生产消费模型的代码自我实现了一份。所以此时我们知道,对于生产消费模型来说,首先它要有一个缓冲区,也就是一个容器,可以便于线程读取或者写入数据,其次是要有两种不同的线程,一种执行生产过程,一种执行消费过程,最后要让这两种不同的线程,也就是生产者和消费者满足它们之间的三种关系(生产者和生产者互斥、消费者和消费者互斥、生产者和消费者互斥且同步)。注意:
具体为什么生产者和消费者需要是同步且互斥的关系之前我们讲过,从反例来看,如果允许生产者和消费者同时进行,也即是多线程之间一个线程读取一个线程写入,那么此时就会导致当某个读取线程没有对应的数据可读时,它就必须阻塞在哪里等待写入线程写入,也就是违背了生产消费模型的初衷,没有很好的实现不同线程之间的解耦。所以在生产消费模型中,我们就一定需要让生产者和消费者保持同步状态,也就是当缓冲区中有数据时,才允许通知读取线程来读数据,没数据时,在写入线程写入的同时,让读取线程可以去申请别的共享资源,而不是阻塞。在此原理之上,互斥关系随之产生。
从代码分析生产消费模型
对上述知识又有了一定理解之后,此时我们就从代码来分析一下上述我们说的一个缓冲区,两种线程,三种关系。当然我们此时是以最简单的生产消费模型下手,在缓冲区中我们并没有对不同区域进行同步,而是对整体进行同步,并且我们明白,对于生产消费模型来说它不局限任何类型的数据,所以对于存储数据的地方来说,我们使用的是模板类型。
1.首先是一个缓冲区
同理上篇博客中所说,此时我们的生产消费模型是基于BlockQueue队列作为缓冲区实现的,具体代码如下图所示:
2.其次是两种不同的线程
搞定了上述生产消费模型中缓冲区相关的知识,其余知识就较为容易,本质就是在利用该缓冲区进行数据的写入和读取而已,所以此时我们就来看看生产者和消费者具体是如何对BlockQueue进行数据的读取和写入吧!具体代码如下所示:
当然由于我们在BlockQueue(缓冲区)中使用了互斥锁进行同步机制保护,所以我们的程序并不在意访问共享资源(缓冲区)的线程数量,并且明白,因为上述consumer接口和productor接口除了BlockQueue提供的接口之外都是局部变量,所以默认是可重入函数,所以如果多个线程同时对其进行访问,此时也不会引发线程安全问题,单线程访问到多线程访问以及代码执行结果代码如下所示:
最后一个知识点,当然在上述代码中,肯定还会存在其它七七八八的问题没有讲解,但是身为优质博主,责无旁贷,只选那种非常不好理解的知识点进行重点讲解,如:此时对于多生产多消费过程,在循环创建线程时,这些线程具体是如何运行的,这个知识点我没有记错的话应该在之前讲解pthread_create接口的使用方式上,我们有进行过一定的理解,现在我们再来好好理解一下:重点在于明白单核和多核的区别,我们都知道多线程之间是允许并行访问的,但是这个并行对于单核来说并不是真实的并行,因为每个线程的执行需要经过调度器的调度,调度器再根据线程的优先级去优先调度优先级高的线程,我们也把这个过程称为时间片轮转调度,所以对于单核执行过程来说,线程之间不是并行执行,而是并发执行,当然并发执行指的也就是多个线程在同一时间段内交替执行,通过快速的切换来模拟同时执行的效果。 也就是说,如果你是在单核条件下,那么上述循环创建线程的执行过程就是依次将所有(20)线程创建完成(操作系统完成),只有当全部线程被创建完成之后,调度器才会根据每个线程的优先级不同进行时间片轮转调度,让它们执行相应的代码,也就是我们上述的consumer/productor接口。明白了单核的线程循环创建过程之后,此时多核同理多个单核,也就是线程之间的执行是地地道道的并行执行,不再是并发执行,所以此时在循环创建线程时,就有可能出现多个线程同时被创建,此时就不需要等待其它线程创建完成(因为多核并行完成),此时就可以根据优先级被调度器直接调度执行了。
3.最后是两种不同线程之间的三种关系
明白了上述两种不同线程和单线程多线程的知识,此时我们对生产消费模型中大部分的硬核知识以及误区都给重点强调了一遍,来到这里我们可以说是无所不能,并且由于生产者和生产者、消费者和消费者、生产者和消费者之间的三种关系我们已经在一个缓冲区中相关代码的实现中体现的淋漓尽致了,所以这块知识没什么好讲的,本质就是通过三种关系通过自己对缓冲区设计,构建出一个在生产消费模型逻辑范围内的属于自己的生产消费模型,具体玩的多花由你自己决定。
深入信号量相关知识
搞定了上篇博客有关生产消费模型的知识,此时我们正式进入该篇博客的重点,有关线程同步的最后一个知识点信号量,在深入学习信号量之前,我们要意识到在很久以前学习有关进程间通信相关知识时,无论是在学习匿名管道、命名管道,还是共享内存,本质我们都是在让两个进程看到“同一份资源”,而当我们谈到这同一份资源时我们很容易就能联想到目前学习有关线程的知识。所以同理,当时我们在学习进程间通信了解信号量的本质就是为了让信号量去保护进程间看到的那一份共享资源,只是当时我们没有线程的概念、没有同步与互斥的概念,只知道想让两个进程看到同一份资源的前提是让它们先看到同一份信号量资源,同理当时我们举过的购买电影票的实例(对共享资源进行预定)。而现在当我们有了线程、线程安全的概念,有了并行访问、竞态条件的概念,此时我们就知道共享资源是需要受到保护的,否则就会引发线程安全问题,此时我们对信号量的理解就是一个保护共享资源的同步机制。你想要访问某共享资源,前提是你获取到了相应的信号量,只有获取到了信号量,才允许你进行下一步的操作,否则阻塞。
为什么学习信号量?
明白了上述有关知识之后,也就明白了之前学习的信号量是什么和为什么,以及现在学习到信号量是什么,所以我们就有了很强的前后关联性,从而让我们可以更好的深入相关知识。所以此时我们就来谈谈为什么还要深入信号量?首先我们知道信号量是一个用于描述临界资源中资源数量的计数器,它可以通过PV操作来实现对临界资源中可用资源数量的控制。同理上述所说,当我们想要访问某临界资源时,只有获取到了信号量资源,才允许我们进行下一步操作,通过这一特征我们不难发现,信号量的使用和我们使用互斥锁是一样的,只有当某个线程抢到了互斥锁之后,该线程才有访问临界资源的能力,那么我们为什么还需要学习信号量呢?直接使用互斥锁来实现这一原理不就行了吗?原因和我们之前在谈有关生产消费模型时同理,因为对于一份共享资源我不一定需要设置互斥机制,我可以让所有线程同时访问该共享资源来极大的增加吞吐量和提高线程并发效率(同理超市不是只允许一个人逛),所以此时我可以将共享资源通过加锁和条件变量,反正就是用同步机制的方式分为一个一个不同的区域,当所有线程在访问该共享资源时,每一个区域中的数据因为被同步机制保护,同时只允许一个线程访问,所以在这样的控制方式下,我们的程序不仅不会出现线程安全问题,而且可以大幅度的提高该临界资源的吞吐量和线程的并发执行效率。所以为了实现这一目的,此时就需要有一个计数器来记录当前共享资源中可用资源的数量,也就是我们上述所说的信号量,所以像互斥锁原理那样的信号量被我们称为二元信号量(1->P->0->V->1),反之称为多元信号量(n->P->n-1->P/V->n-2/n)。
注意:
因为信号量也是共享资源,同理需要通过原子性实现。并且明白,当一个信号量被申请成功,那么就表示共享资源中一定有某块临界资源已经预定给我使用了(买票原理),所以信号量的预定机制本质就是把判断某临界资源是否可用转换为申请信号量是否成功。 所以在以后的多线程环境编码中,我们就可以直接使用信号量的方法来代替对临界资源是否可用的判断条件,具体有待通过具体场景和代码分析。
信号量相关接口认识
在以前学习信号量的过程中,我们也对信号量相关接口进行了一定的了解,但是注意,当时我们了解的是SystemV标准,而今天我们学习的是POSIX标准,无论是SystemV标准还是POSIX标准,它们本质都只是操作系统中用于进程间通信和同步的两个标准接口,这两个版本具体有什么区别,可参考该篇博客:SystemV和POSIX简介和对比 反正本质就是通信界大佬们在早些年定下的两个标准。
1.SystemV标准信号量
其中在以前学习SystemV标准时,了解的接口有semget()、semctl()、semop()
,按照先后顺序它们的作用分别是创建信号量、删除信号量和对信号量加减。具体如何使用可回顾之前学习信号量时的博客:之前的信号量博客
2.POSIX标准信号量
明白了上述知识之后,我们来看看POSIX标准下的信号量接口,此时可分为初始化信号量、销毁信号量和等待信号量,具体使用形式如下所示:
- 初始化信号量:
int sem_init(sem_t* sem,int pshared,unsigned int value);
第一个参数同理互斥锁和条件变量表示信号量参数的地址,第二个参数是一个区分进程间通信和线程间通信的参数(0表示线程,非0表示进程),第三个参数表示信号量的初始值,也就是该共享资源中可用资源的数量。 - 等待信号量:
int sem_wait(sem_t* sem);
该接口起到P操作,也就是对信号量进行减减操作,同理,要等待信号量的地址。 - 发布信号量:
int sem_post(sem_t* sem);
同理,该接口起到V的操作,也就是对信号量进行加加的操作,参数同理信号量的地址。 - 销毁信号量:
int sem_destroy(sem_t* sem);
同理,要销毁信号量的地址。
基于环形队列的CP问题
搞定了上述有关信号量的知识,此时我们就需要进入实操阶段,也就是在具体的场景中使用信号量接口来完成对一份共享资源中资源数量的控制,本质也就是使用代码来维护我们自己的线程规则。当然因为时间原因,所以这部分知识我们留到下篇博客再见把!
总结:有关信号量相关的知识,本质就是想让我们见识更加高级的CP(生产消费模型)知识,从而彻底的将多线程相关知识给搞定,See you!
相关文章:

学习系统编程No.34【线程同步之信号量】
引言: 北京时间:2023/7/29/16:34,一切尽在不言中,前几天追了几部电视剧,看了几部电影,刷了n个视屏,在前天我们才终于从这快乐的日子里恢复过来,然后看了两节课,也就是上…...

SolidUI社区-Snakemq 通信源码分析
背景 随着文本生成图像的语言模型兴起,SolidUI想帮人们快速构建可视化工具,可视化内容包括2D,3D,3D场景,从而快速构三维数据演示场景。SolidUI 是一个创新的项目,旨在将自然语言处理(NLP)与计算机图形学相…...

【大数据之Flume】四、Flume进阶之复制和多路复用、负载均衡和故障转移、聚合案例
1 复制和多路复用 (1)需求:使用 Flume-1 监控文件变动(可以用Exec Source或Taildir Source),Flume-1 将变动内容传递给 Flume-2(用Avro Sink传),(用Avro Sou…...

前端学习--vue2--插槽
写在前面: 这个用法是在使用组件和创建组件中 文章目录 介绍简单使用多个插槽省写默认/后备内容作用域插槽常用实例Element-ui的el-table 废弃用法slot attributeslot-scope attribute 介绍 我们在定义一些组件的时候,由于组件内文字想要自定义&#…...

使用 Docker Compose 部署 Redis Cluster 集群,轻松搭建高可用分布式缓存
Redis Cluster(Redis 集群)是 Redis 分布式解决方案的一部分,它旨在提供高可用性、高性能和横向扩展的功能。Redis Cluster 能够将多个 Redis 节点组合成一个分布式集群,实现数据分片和负载均衡,从而确保在大规模应用场…...

在Spring Boot框架中集成 Spring Security
在Spring Boot框架中集成 Spring Security 目录 技术介绍SpringSecurity的核心功能:SpringSecurity特点:具体实现 1、集成依赖2、修改spring security实现service.impl.UserDetailsServiceImpl类 代码1具体解释代码2具体解释 实现config.SecurityConfi…...

登月再进一步:Apollo自动驾驶的里程碑
前言 「作者主页」:雪碧有白泡泡 「个人网站」:雪碧的个人网站 「推荐专栏」: ★java一站式服务 ★ ★前端炫酷代码分享 ★ ★ uniapp-从构建到提升★ ★ 从0到英雄,vue成神之路★ ★ 解决算法,一个专栏就够了★ ★ 架…...

嵌入式一开始该怎么学?学习单片机
学习单片机: 模电数电肯定必须的,玩单片机大概率这两门课都学过,学过微机原理更好。 直接看野火的文档,芯片手册,外设手册。 学单片机不要纠结于某个型号,我认为stm32就OK,主要是原理和感觉。…...

Spring事件监听器ApplicationListener
目录 介绍 spirng启动后启动某方法 介绍 ApplicationEvent以及Listener是Spring为我们提供的一个事件监听、订阅的实现,内部实现原理是观察者设计模式,设计初衷也是为了系统业务逻辑之间的解耦,提高可扩展性以及可维护性。事件发布者并不需…...

安全学习DAY10_HTTP数据包
HTTP数据包 文章目录 HTTP数据包小节导图Request请求数据包结构Request请求方法(方式)请求头(Header)Response响应数据包结构Response响应数据包状态码状态码作用:部分状态码详解判断网站文件是否存在的状态码…...

云原生落地实践的25个步骤
一、什么是云原生? 云原生从字面意思上来看可以分成云和原生两个部分。 云是和本地相对的,传统的应用必须跑在本地服务器上,现在流行的应用都跑在云端,云包含了IaaS,、PaaS和SaaS。 原生就是土生土长的意思,我们在开始…...

Stable diffusion 三大基础脚本 提示词矩阵,载入提示词,XYZ图表讲解
目录 0.本章讲解 1.提示词矩阵(prompt matrix) 1.2.提示词矩阵功能选项 1.2.1.把可变部分放在提示词文本的开头 1.2.2.为每张图片使用不同随机种子 1.2.3.选择提示词 1.2.4.选择分割符 1.2.5.宫格图边框(像素) 2.从文本框或文件载入提示词(Pro…...

uniapp uni-combox 下拉提示无匹配项(完美解决--附加源码解决方案及思路)
问题描述 匆匆忙忙又到了周一啦,一大早就来了一个头疼的问题,把我难得团团转,呜呜呜~ 下面我用代码的方式展示出来,看下你的代码是否与我的不同。 解决方案 <uni-forms-item label"名称" name"drugName&quo…...

10. Mybatis 项目的创建
目录 1. Mybatis 概念 2. 第一个 Mybits 查询 2.1 创建数据库和表 2.2 添加 Mybatis 框架支持 2.3 添加配置文件 2.4 配置 MyBatis 中的 XML 路径 2.5 添加业务代码 在学习 Mybatis 之前,我们需要知道 Mybatis 和 Spring 没有任何的关系。如果一定要强调二者…...

历年 Nobel prize in Physics (诺贝尔物理学奖)简介
历年 Fields Medal 与 Nobel prize in Physics 简介 Nobel prize in Physics 1901年12月10日 诺贝尔逝世5周年纪念日首次颁发诺贝尔奖。1916年 第一次世界大战 1914.7 至 1918.11诺贝尔物理学奖空缺1931年诺贝尔物理学奖空缺1934年诺贝尔物理学奖空缺1940年—1942年 第二次世界…...

IDEA中Git面板操作介绍 变基、合并、提取、拉取、签出
IDEA中Git面板操作介绍 变基、合并、提取、拉取、签出 面板介绍 变基、合并 提取、拉取 签出、Checkout 面板介绍 如图,在IDEA的Git面板中,仓库会分为本地仓库和远程仓库,代码仓库里面放的是各个分支。 分支前面的书签🔖标志…...

Android Studio开发简易APP添加代办事项
创建xml布局页 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width...

python 统计所有的 仓库 提交者的提交次数
字典去重 YYDS 然后再写入excel 表 yyds #!/bin/env python3 from git.repo import Repo import os import pandas as pdspath "/home/labstation/workqueue/sw" url "git10.0.128.128" date [str(x) for x in range(202307, 202308)] datefmt "%…...

018-从零搭建微服务-系统服务(五)
写在最前 如果这个项目让你有所收获,记得 Star 关注哦,这对我是非常不错的鼓励与支持。 源码地址(后端):https://gitee.com/csps/mingyue 源码地址(前端):https://gitee.com/csps…...

HarmonyOS 开发基础(三)登录页面单向数据绑定(父组件向子组件传参)
一、目录结构认识 开发软件目录截图部分文件夹说明 文件组织结构图 二、完成单向数据绑定 index.etx // 导出方式直接从文件夹 import MyInput from "../common/commons/myInput" Entry Component /* 组件可以基于struct实现,组件不能有继承关系&am…...

发npm包
重点文件 .github -> workflow -> .yml文件 发自己的包 新建dev分支,合并到master后自动执行 fork别人的包 fork -> base dev新建本地rebase-dev分支 -> 提交push后合并至dev -> dev合并至master后自动执行 值得注意的是,fork别人的…...

<el-empty>
<el-empty> 是 Element UI 框架中提供的一个组件,用于显示空状态的占位内容。Element UI 是一套基于 Vue.js 的组件库,用于构建响应式和易用的用户界面。 <el-empty> 组件在应用中常用于以下场景: 当数据为空时,可以…...

IO流(4)- 序列化流与反序列化流
目录 1. 序列化流与反序列化流的基本介绍 2. 序列化流的基本用法? 3. 序列化流的作用? 4. 反序列化流的基本用法? 5. 反序列化流的作用 6. 序列化流与反序列化流使用时需要注意的细节(非常重要) 6.1 被序列化的…...

人工智能如何应对 DevOps 监控和可观测性挑战
自 ChatGPT 横空出世之后,AIGC 已成为不可逆转的时代浪潮。在之前的文章中,我们介绍了DevOps 领域中AI的用例,需要回顾可以点击下方链接。在本篇文章中,我将简单聊聊人工智能(AI)如何通过分析日志和指标来预…...

数字化新时代,VR全景拍摄与制作
导语: 随着科技的飞速发展,数字化图片正在引领新的时代潮流。在这个数字化图片的新时代,VR全景拍摄与制作技术正以其独特的特点和无限的优势,成为数字影像领域的一颗璀璨明星。让我们深入了解VR全景拍摄与制作的特点和优势&#…...

uniapp 权限说明
android.permission.ACCESS_CHECKIN_PROPERTIES 访问登记属性 读取或写入登记check-in数据库属性表的权限 android.permission.ACCESS_COARSE_LOCATION 获取错略位置 通过WiFi或移动基站的方式获取用户错略的经纬度信息,定位精度大概误差在30~1500米 android.permission.ACCESS…...

3D Web轻量化渲染开发工具HOOPS Communicator是什么?
HOOPS Communicator是Tech Soft 3D旗下的主流产品之一,具有强大的、专用的高性能图形内核,是一款专注于基于Web端的高级3D工程应用程序。由HOOPS Server和HOOPS Web Viewer两大部分组成,提供了HOOPS Convertrer、Data Authoring的模型转换和编…...

心法利器[93] | 谈校招:技术面
心法利器 本栏目主要和大家一起讨论近期自己学习的心得和体会,与大家一起成长。具体介绍:仓颉专项:飞机大炮我都会,利器心法我还有。 2022年新一版的文章合集已经发布,累计已经60w字了,获取方式看这里&…...

excel英语翻译让你的数据更容易被理解
从前有一个名叫小明的办公室职员,他每天都要处理大量的数据和报表。然而,由于工作需要,他经常收到来自不同国家的Excel表格,这些表格上的内容都是用各种各样的语言编写的,让他很难理解其中的意思。这时,小明…...

RK3588S之CPU--benchmark(二)
目录 一、引言 二、benchmark测试工具 ------>2.1、Geekbench ------------>2.1.1、下载移植 ------------>2.1.2、跑分结果 ------------>2.1.3、跑分榜 ------>2.2、Spec06 ------------>2.2.1、spec06介绍 ------------>2.2.2、下载移植(包含…...