Linux线程同步(下)

news2024/11/28 13:42:53

在这里插入图片描述

文章目录

  • 1. POSIX信号量
  • 2. 基于环形队列的生产消费模型
    • 2.1 代码实现
      • 2.1.1 构造函数和析构函数
      • 2.1.2 生产和消费
      • 2.1.3 测试
  • 3. 线程池
    • 3.1 成员变量
    • 3.2 构造和析构
    • 3.3 push和pop
    • 3.4 启动线程池
    • 3.5 测试
  • 4. 将线程池改成单例模式
  • 5. STL、智能指针和线程安全
  • 6. 其他常见的各种锁
    • 6.1 自旋锁的概念
  • 7. 读者写者问题
    • 7.1 读写锁
    • 7.2 使用读写锁

1. POSIX信号量

POSIX信号量和SystemV信号量作用相同,都是用于同步操作,达到无冲突的访问共享资源目的。 但POSIX可以用于线程间同步

从前面的学习,我们知道:信号量是一个计数器,描述临界资源数量的计数器。只要信号量申请成功,那么就一定能获取指定的资源

临界资源可不可以看作一个个小部分,被多个线程并发执行呢
结合一定的场景,一个线程执行临界资源的一小部分是可以的,它们并不冲突

我们知道:访问临界资源前,需要申请锁和释放锁,假设信号量为1,那么信号量由1到0的过程就是加锁,信号量由0到1的过程就是解锁。这个也叫做二元信号量,也就是互斥锁。

初始化信号量:
在这里插入图片描述

销毁信号量:
在这里插入图片描述
等待信号量:
在这里插入图片描述
发布信号量:
在这里插入图片描述

2. 基于环形队列的生产消费模型

上一节生产者-消费者的例子是基于queue的,其空间可以动态分配。现在基于固定大小的环形队列重写这个程序。

环形队列采用数组模拟,用模运算来模拟环状特性

如果大家不懂环形队列,可以看这篇文章:环形队列的讲解
环形结构起始状态和结束状态都是一样的,不好判断为空或者为满,所以可以通过加计数器或者标记位来判断满或者空。另外也可以预留一个空的位置,作为满的状态。但是我们现在有信号量这个计数器,就很简单的进行多线程间的同步过程。

环形队列什么时候会发生访问同一个位置
当只有空和满的时候,头和尾会指向同一个位置
当环形队列为空的时候,只能让生产者先走,消费者不能走。当环形队列为满的时候,只能让消费者先走,生产者不能走。
从这里我们可以看出:它是具备同步和互斥关系的
这个是由信号量来保证的。

那么其它时候,指向的是不同位置。指向不同位置,也就是指向不同的临界资源,那么生产者和消费者是可以进行并发的。

那么生产生和消费者最关心的资源是什么
生产者最关心的是空间,消费者最关心的是数据
假设环形队列一开始有N个空间,那么生产者的信号量(roomSem)一开始就是从N开始,然后到0。消费者的信号量(dataSem)一开始从0开始,然后到N
那么生产线程首先需要P(roomSem)申请空间,这样空间信号量会少一个,然后放数据到空间里。最后V(dataSem),因为数据的信号量会增加一个。
消费线程首先需要P(dataSem)将数据的信号量减1,然后消费数据,最后V(roomSem),因为空间就会多出来一个。

2.1 代码实现

2.1.1 构造函数和析构函数

在这里插入图片描述
这个是环形队列的成员变量。然后我们需要将它们初始化和析构。

信号量如何初始化和释放的呢?我们在上面已经了解过:
在这里插入图片描述
信号量的初始化,第一个参数是你要初始化的信号量,第二个参数意思是你是否要共享,我们在这先设置为0,第三个参数是信号量的初始值。

在这里插入图片描述
这是信号量的释放,比较简单。
在这里插入图片描述

2.1.2 生产和消费

根据我们上面原理的分析,按照顺序来写:
在这里插入图片描述

2.1.3 测试

在这里插入图片描述
我们让生产者慢点,这样消费者就会按照生产者的顺序来。

运行结果是:
在这里插入图片描述
生产一个消费一个。

但是这存在一个问题:这里是单生产者,单消费者。如果是多生产者,多消费者会有什么问题呢
在这里插入图片描述
假设信号量roomSem为20,然后有5个线程,那么就可能都申请到了信号量,那么就会同时进行生产,那么就会同时访问pIndex_,就会把其它线程的数据给覆盖了。消费者也是一样的道理。那么我们就需要让消费者和生产者各自加锁。
在这里插入图片描述
加上锁之后,就只能有一个线程竞争到锁,然后再去竞争信号量。但是这样的信号量无法被多次的申请。
在这里插入图片描述
那么我们的线程就是先申请信号量,也就是说你先占据了资源,然后再去申请锁。这样在某种程度上可以提高效率。

3. 线程池

线程池:一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。

大致过程如下:
在这里插入图片描述
如果有任务就放到任务队列里面,然后线程去任务队列去获取,如果任务队列里面没有,线程就阻塞等待。

3.1 成员变量

在这里插入图片描述
如果没有任务时,让线程在条件变量下去等,有任务时就唤醒某一个线程。我们知道:条件变量本来就是排队的,没有任务时,线程都在排队,有任务时,就唤醒某个线程去执行,这样就可以实现多线程负载均衡,按照轮询方式去执行对应的任务。

3.2 构造和析构

在这里插入图片描述
当我们第一次构造的时候,可以判断线程的个数,以防有人传恶意数据。将isStart设置成false,说明还没有启动。

3.3 push和pop

既然如此,我们需要放任务和拿任务,push可以是公有的,pop可以设置私有:
在这里插入图片描述
既然生产了任务,说明任务队列里面有任务了,可以选择一个线程去执行。也就是随机唤醒一个线程。

在这里插入图片描述

3.4 启动线程池

在这里插入图片描述
我们启动时先判断这个线程池有没有启动过,如果已经启动过就报错。如果没启动就先启动,然后把isStart设置true。

但是这里有一个问题,我们先来测试一下:
在这里插入图片描述
我们编译一下:
在这里插入图片描述
原因是:threadRoutine这个回调函数是类里面的成员函数,以前都是在类外的定义。在类里面的成员函数是有隐藏的this指针,所以我们本应该传两个参数

解决办法:加个static修饰这个成员函数
在这里插入图片描述
那么我们用static修饰了,那么函数就不能使用this指针了,也就不能访问类的成员变量了。
然后我们创建线程的时候再传this指针过去,就能访问成员了。

然后我们需要让线程去执行对应的任务:
在这里插入图片描述
我们让每个线程分离。这样就不需要等待了。获取线程池对象的指针就可以访问类的成员函数和成员变量。
如果任务队列里面有任务,我们就可以取出来,去执行。
在这里插入图片描述
这里的Log()是一个日志打印函数:
在这里插入图片描述

3.5 测试

前面写过一个计算的任务:
在这里插入图片描述
然后我们以主线程去派发任务:
在这里插入图片描述
运行结果:
在这里插入图片描述

4. 将线程池改成单例模式

如果不知道单例模式,可以看这篇文章:单例模式
我们以懒汉模式为例:
在这里插入图片描述
静态的成员变量需要在类外定义。
在这里插入图片描述
把构造函数设置成私有,析构为公有。然后在写一个能获取这个对象的函数:
在这里插入图片描述
我们知道这里是会有线程安全的,第一次调用 getInstance 的时候,如果两个线程同时调用,可能会创建出两份 T 对象的实例。那么我们就需要加锁。这里我们用的是一个RAII思想的锁:
在这里插入图片描述
在这里插入图片描述
我们定义了一个static的锁,也就是全局的,它可以自动构造和释放。

那么我们怎么使用呢?
在这里插入图片描述
在这里介绍一个函数:
在这里插入图片描述
这个函数是设置线程的属性。
在这里插入图片描述
在这里插入图片描述
我们给主线程设置姓名为master,给新线程姓名为follower。
在这里插入图片描述
这里意思是匹配其中一个就行了,可以看到线程的名字改了。

5. STL、智能指针和线程安全

在这里插入图片描述

6. 其他常见的各种锁

在这里插入图片描述

6.1 自旋锁的概念

在这里插入图片描述
在临界区中,我们没有讨论过临界区里的时间问题。如果在临界区里等待时间短的话,就比较适合轮询测试是否就绪。如果在临界区里等待时间长的话,就比较适合挂起等待
之前,我们用的锁都是挂起等待锁,也就是默认按照等待时间长的来加锁的。如果我们想用轮询测试的方式,我们就可以用自旋锁(pthread_spin_lock)。它的接口和mutex是一样的,就是换成spin。

7. 读者写者问题

7.1 读写锁

在编写多线程的时候,有一种情况是十分常见的。那就是,有些公共数据修改的机会比较少。相比较改写,它们读的机会反而高的多。通常而言,在读的过程中,往往伴随着查找的操作,中间耗时很长。给这种代码段加锁,会极大地降低我们程序的效率。那么有没有一种方法,可以专门处理这种多读少写的情况呢? 有,那就是读写锁。

它有1个读写场所,2种角色:读者和写者,3种关系:写者和写者是互斥的关系,读者和读者没有关系,读者和写者是互斥关系(因为在写的时候,我们读的话可能数据不准确)

那么为什么前面消费者和消费者之间是互斥关系,这里读者和读者之间没有关系呢
原因是:消费者会把数据拿走,而读者不会

既然读者和写者的数量比是n:1的,那么它们进入临界区的时候,如何判断是读者还是写者?这就需要用到读写锁了。
读者:加读锁,然后读取内容,释放锁。
写者:加写锁,写入修改内容,释放锁

在这里插入图片描述
这个是读写锁的初始化和销毁。
在这里插入图片描述
这个是读者的加锁。
在这里插入图片描述
这个是写者的加锁。
在这里插入图片描述
这个是解锁,解锁都是一样的。

7.2 使用读写锁

那么读者就加读锁,写者就加写锁,那么就可以分辨两种角色了。
在这里插入图片描述
这就是读写锁的使用。

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

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

相关文章

聊聊测试驱动开发

这是鼎叔的第六十四篇原创文章。行业大牛和刚毕业的小白,都可以进来聊聊。 欢迎关注本专栏和微信公众号《敏捷测试转型》,星标收藏,大量原创思考文章陆续推出。 本文观点参考自Lasse Koskela,他是《测试驱动开发的艺术》的作者。…

软件测试之【单元测试、系统测试、集成测试】

一、单元测试的概念 单元测试(Unit Testing)是对软件基本组成单元进行的测试,如函数(function或procedure)或一个类的方法(method)。当然这里的基本单元不仅仅指的是一个函数或者方法&#xff0…

揭秘跨部门沟通的秘密武器:让不归你管的人主动配合你的绝妙方法!

跨部门沟通,Edge对此有点胆怯:“我们自己内部进度,怎么着都好管。都是自己人,目标一致。可涉及跨部门合作,管起来就困难。人家又不归我们管,不可控因素太多了。如果在合作的过程中,出现啥问题&a…

docker 镜像结构原理

目录 参考文档:第八篇:Docker镜像结构原理_Linux运维开发的技术博客_51CTO博客 1、基础镜像 base base 镜像有两层含义: 为什么我们的镜像文件比一般的软件小一些呢? 二、镜像的分层结构 问什么 Docker 镜像要采用这种分层结…

org.yaml.snakeyaml.parser.ParserException: while parsing a block mapping

一、yml解析异常问题 今天启动某开源项目时,碰到一个问题org.yaml.snakeyaml.parser.ParserException: while parsing a block mapping 。 二、解决 2.1 修改项目目录下缩进格式 在项目下的nacos目录中application-common.yml 文件中修改缩进格式,每…

15个最好的性能测试工具(软件测试工程师必备)

在软件测试日常工作中,大家接触得比较多的性能测试工具有LoadRunner和Jmeter,这里整理了web应用程序性能和负载压力能力的最广泛使用的性能测试工具的综合列表。 这些负载测试工具将确保您的应用程序在高峰流量和极端压力条件下的性能。 该列表包括开源…

Spring Security OAuth2.0(五)-----OAuth2实现自定义统一认证登录页/自定义授权页/基于mysql存储数据

本次实例涉及三个项目 核心项目工程unify_authorization_server(认证授权登录) 资源服务器项目unify_resource_server 测试项目是前面几篇写的项目 这里没有改动直接用来测试实例项目 (一)unify_authorization_server pom相关依赖 我采用的是spring-bo…

Ngnix网站服务

Ngnix网站服务 Ngnix网站服务 一、Ngnix服务基础:1.Ngnix的基本概述:2.简述Nginx和Apache的差异:3.Nginx和Apache的优点:4.应用场景:5.扩展: 二、编译安装Ngnix服务:1.编译安装:2.常…

基于matlab使用自校准来适应阵列不确定性(附源码)

一、前言 此示例显示了基于约束优化过程的自校准过程。利用机会来源同时估计阵列形状的不确定性和来源方向。此示例需要优化工具箱。 理论上,可以设计一个完美的均匀线性阵列(ULA)来执行各种处理,例如波束成形或到达方向估计。通常…

玩转ChatGPT:回答审稿人问题

一、写在前面 前段时间一篇时间序列预测的文章返修,还挺幸运的,给了个小修。 不过问题也问得有点刁钻,应该是个行家。 想到手头有小Chat,打算使用TA来辅助我回答审稿人问题。 以下展示仅仅提供一个工作流和思路,具体…

好程序员:逼自己看完并学会,你的Java会很牛!

打算学java的伙伴们,如果你们很迷茫焦虑的话,不妨看看好程序员的建议。好程序员作为行内人告诉大家,零基础也是可以学java的,而且不仅可以学会,还可以学的很好,并且能找到工作。 Java学习路线规划&#xff…

Day22 实战篇 ——Jmeter性能测试实战——JMeter执行原理、Jmeter性能测试实战、下载使用PerfMon插件、 Grafana可视化展示

Day22 实战篇 ——Jmeter性能测试实战——JMeter执行原理、Jmeter性能测试实战、下载使用PerfMon插件、 Grafana可视化展示 文章目录 Day22 实战篇 ——Jmeter性能测试实战——JMeter执行原理、Jmeter性能测试实战、下载使用PerfMon插件、 Grafana可视化展示一、Jmeter执行原理…

高性能通信库——nanomsg(含交叉编译)

一、nanomsg介绍 NanoMsg是一个Socket的通讯库,使用C语言编写实现的,这样就可以适用于多种操作系统,而且几乎不需要什么依赖,可扩展并且能易于使用。Nanomsg提供了几种常见的通信模式 ( 也称为“可扩展性协议” &#…

Flutter 小技巧之 InkWell Ink 你了解多少

今天要介绍一个「陈年」小技巧,主要是关于 InkWell 的基础科普,InkWell 控件相信大家不会陌生, 作为 Flutter 开发中最常用的点击 Widget ,配合 Flutter 自带的 Material ,可以轻松实现带有水波纹等的点击效果。 而之所…

VSCode 安装配置教程详解包含c++环境配置方法

vscode安装教程及c环境配置详解 vscode下载安装下载C扩展插件VScode C环境配置配置环境变量检查 MinGW 安装配置编译器:配置构建任务检查是否安装了编译器配置完毕 vscode下载安装 地址:官网下载地址 直接打开下载好的.exe文件进行安装即可&#xff0…

如何使用 PowerPoint 2021 制作演示文稿?

软件安装:办公神器office2021安装教程,让你快速上手_正经人_____的博客-CSDN博客 引言 PowerPoint 是一款非常常用的演示文稿制作工具,它可以帮助您创建漂亮的幻灯片,展示您的想法和信息。如果您是 PowerPoint 的新手&#xff…

xx客滑块

xx客滑块 网址流程1、访问首页,得到网页源代码得到 sessionId2、生成dInfo参数(getInfoTp接口使用到),是AES 加密(不校验)3、访问 /captcha/getInfoTp 得到responseId (可以认为是图片id&#x…

上海细化“元宇宙”概念 落地场景仍待破局

日前,一份关于“元宇宙”更加具体的行动方案引发业内高度关注:上海发布《上海市“元宇宙”关键技术攻关行动方案(2023—2025年)》的通知。 这说明政府层面开始进一步细化,以更好推动‘元宇宙’产业的发展。” 主攻沉…

【强烈推荐】基于STM32的TFT-LCD各种显示实现(内容详尽含代码)

前言:TFT-LCD模块作为人们日常生活中常见屏幕类型之一,使用的受众面非常广阔。例如:显示各个传感器数值,显示精美界面,多级化菜单系统等等都不离不开他的身影。可以说学会TFT-LCD模块是嵌入式开发必须掌握的驱动开发技…

AOP切面记录日志

AOP切面记录日志 一、导包 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency>二、写一个注解 /*** 用于切面记录日志用的注解&#xff0c;只能加在方法中使用* a…