POSIX信号量(基于生产消费模型)

news2024/12/30 3:24:48

目录

🍊一、信号量

1.1之前代码的不足之处

1.2什么是信号量

 🍊二、信号量接口

🍊三、信号量版本的生产消费模型

①单生产单消费

②多生产多消费

🍊四、线程池

 🍊五、线程安全的单例模式

🍊六、其他常见的各种锁

①STL,智能指针和线程安全

②其他常见的各种锁

🍊一、信号量

1.1之前代码的不足之处

 一个线程在操作临界资源的时候,临界资源必须是满足条件的。可是,公共资源是否满足生产或者消费条件,我们无法直接得知(在没有访问前,无法得知)。如果事前得知满足条件,就不需要加锁让线程可以直接访问。而现在我们的代码是先加锁,再检测,再操作,再解锁。是否可以在加锁之前就检测条件呢?这样可以事前分配,提高效率!这也就是POSIX信号量的作用!

1.2什么是信号量

在谈及进程间通信时提及过信号量,没错,通俗来说,信号量就是计数器

它就是对临界资源进行事前预订这样一个机制。只要申请到,用有信号量,未来就一定能够拥有临界资源的一部分。这也就是申请信号量的本质:对临界资源中特定小块资源的预订机制。

申请成功的一定知道能够使用,而失败的只能等待。这样由之前的先加锁再检测的代码可以优化为不检测资源,只需要检测信号量就可以了。

 🍊二、信号量接口

 引入环形队列&&cp问题

 如果是使用环形队列来当作生产消费的缓冲区。那么我们就要考虑生产和消费在什么情况下可能访问同一个位置:

1、空的时候

2、满的时候

其他情况下生产者和消费者根本访问的就是不同的区域!

环形队列有两种方式模拟:

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

 🍊环形结构起始状态和结束状态都是一样的,不好判断为空或者为满,所以可以通过加计数器或标记位来判断满或者空。另外也可以预留一个空的位置,作为满的状态。

我们代码采用的是第一种方式。 

 为了完成环形队列cp问题,我们要做的核心工作是什么?

1、消费者不能超过生产者(因为是先生产后消费)

2、生产者不能超过消费者一圈

3、考虑生产者什么时候和消费者站在一起

对于生产者而言看中的是队列中的剩余空间--空间资源定义一个信号量;而对于消费者而言看中的是放入队列中的数据--数据资源定义一个信号量。

 环形队列为空的时候,生产者可以申请成功,而消费者无法申请成功。当为满的时候,消费者可以申请成功,而生产者无法申请成功。

 

 在这里,生产和消费的位置我们一定要想清楚,其实就是队列中的下标,而且当为空或者满的时候二者下标相同。

 代码是生产慢于消费的,演示也是如此,非常典型的同步过程。

如果消费慢于生产,看到的现象是生产者先生产满队列,消费者一消费,生产者就生产,消费一个生产一个。

 消费的是历史数据。

🍊三、信号量版本的生产消费模型

①单生产单消费

 

 

 让生产消费比较复杂的类。

 目前只是支持了单生产和单消费的互斥和同步。

②多生产多消费

 

 

 申请到了空间信号量,意味着一定能进行正常的生产。那么我们是先加锁再申请信号量还是先申请信号量再加锁呢?

之前的代码:

 效率更高的是先分配信号量,再加锁。在持有锁期间,可以提前先把信号量指派给不同的线程。先申请加锁再申请信号量效率非常低,意味着后续线程只能在锁的位置等。

 总的代码逻辑就是申请自己关心的资源,释放对方关心的资源。

🍊四、线程池

线程池的概念:

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

线程池的应用场景:

1、需要大量的线程来完成任务,且完成任务的时间比较短。WEB服务器完成网页请求这样的任务,使用线程池技术是非常合适的。因为单个任务小,而任务数量巨大,你可以想象一个热门网站的点击次数。但对于长时间的任务,比如一个Telnet连接请求,线程池的优点就不明显了。因为Telnet会话时间比线程的创建时间大多了。

2、对性能要求苛刻的应用,比如要求服务器迅速相应客户请求。

3、接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。突发性大量客户请求,在没有线程池情况下,将产生大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限,出现错误。

线程池实例

1、创建固定数量线程池,循环从任务队列中获取任务对象。

2、获取到任务对象后,执行任务对象中的任务接口。

线程池的简单实现:

 

 

 

 

 

 🍊五、线程安全的单例模式

单例模式是一种"经典的,常用的,常考的"设计模式。

单例模式的特点:

某些类,只具有一个对象(实例),就称之为单例。

在很多服务器开发场景中,经常需要让服务器加载很多的数据(上百G)到内存中。此时往往要用一个单例的类来管理这些数据。

非常经典的单例模式是饿汉实现方式懒汉实现方式

以洗碗的例子来比拟这两种模式:

吃完饭 , 立刻洗碗 , 这种就是饿汉方式 . 因为下一顿吃的时候可以立刻拿着碗就能吃饭 .
吃完饭 , 先把碗放下 , 然后下一顿饭用到这个碗了再洗碗 , 就是懒汉方式 .
懒汉方式最核心的思想是"延时加载",从而能够优化服务器的启动速度。
简单来说饿汉就是提前开好了空间放在那里让我们去用,而懒汉就是口头上说开了空间,实际是等到我们使用的时候再开空间。

上面的代码只需修改两处就能改成懒汉单例模式:

 

 

🍊六、其他常见的各种锁

①STL,智能指针和线程安全

STL中的容器是否是线程安全的?

不是。原因是STL的设计初衷是将性能挖掘到极致,而一旦涉及到加锁保证线程安全,会对性能造成巨大的影响。而且对于不同的容器,加锁方式的不同,性能可能也不同。因此STL默认不是线程安全,如果需要在多线程环境下使用,往往需要调用者自行保证线程安全。

智能指针是否是线程安全的?

对于 unique_ptr,由于只是在当前代码块范围内生效,因此不涉及线程安全问题。

对于shared_ptr,多个对象需要共用一个引用计数变量,所以会存在线程安全问题,但是标准库实现的时候考虑到了这个问题,基于原子操作(CAS)的方式保证shared_ptr能够高效,原子的操作引用计数。

②其他常见的各种锁

悲观锁:在每次取数据时,总是担心数据会被其他线程修改,所以会在取数据前先加锁(读锁,写锁,行锁等),当其他线程想要访问数据时,被阻塞挂起。

乐观锁:每次取数据时候,总是乐观的认为数据不会被其他线程修改,因此不上锁。但是在更新数据前,会判断其他数据在更新前有没有对数据进行修改。主要采用两种方式:版本号机制和CAS操作。

CAS操作:当需要更新数据时,判断当前内存值和之前取得的值是否相等。如果相等则用新值更新。若不等则失败,失败则重试,一般是一个自旋的过程,即不断重试。

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

这里可以简单介绍一下自旋锁和读写锁。

自旋锁

当一个线程在询问临界资源时,有两种方式,一种是挂起等待其他线程访问完临界资源,另一种就是自旋(轮询)。采用哪种方式取决于需要等待的时长问题。比如这个线程充满大量的IO操作,等待时间较长,就挂起等待。而具体采用哪种方式是程序员自己定义的,都不确定的时候可以自行测试确定效果。

它的一些接口和互斥锁设计比较相似:

 读写锁

 根据读写锁可以设计读者写者模型,它和生产和消费者模型的本质区别是消费者会拿走数据,而读者不会。

读者写者模型应用场景就是一次发布,很长时间不做修改的,比如笔者记的笔记。这其中类似于生产消费模型也是"321"设计规则。

三种关系:写者之间是互斥关系。一旦有一个写者,其余写者就无法进入。读者和写者之间是互斥和同步关系。一旦写者进入,读者就无法读取数据,而读者进入,写者在一定情况下和读者是同步关系(涉及到读者优先和写者优先的设计)。读者与读者没有关系,可共享读取数据。

两种角色:读者和写者。一个交易场所。

读写锁的一些接口:

 伪代码原理:

 这是读者优先的伪代码设计。如果是第一次读者进入,那么根据读者和写者互斥就锁住写锁,当读者的数量为0时,就释放写锁。

 读者优先很容易导致写者饥饿问题,这是很正常的,只要有读者来,写者一直等待。而写者优先的设计就是写者能拦住没有进去的读者。以写者来的时间点来划分,晚于写者的读者就阻塞,先于写者的就可以读取。等之前的读者读完,写者处理完再让后续读者读取。

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

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

相关文章

大模型高效微调综述下: DiffPruning、BitFit、LoRa、AdaLoRA、MAM Adapters、UniPELT

文章目录 四、Selective Methods4.1 DiffPruning(2020.10)4.2 BitFit(2021.6)4.3 Freeze and Reconfigure (FAR,2022)4.4 FishMask(略) 五、Reparametrization-based methods(重参数…

Selenium自动化工具集 - 完整指南和使用教程

文章目录 Selenium 的概述:Selenium 的安装与环境配置:Selenium WebDriver 的基本概念:定位元素的方法:常用操作方法:获取所有的 cookie:获取指定名称的 cookie:添加 cookie:删除指定…

JavaFX应用开发教程——基于JDK9与NetBeans实现

ISBN: 978-7-302-61499-9 作者:宋波 页数:257页 阅读时间:2023-06-18 推荐指数:★★★★★ 《JavaFX应用开发教程——基于JDK9与NetBeans实现》 是目前市面上讲解Java桌面开发为数不多的教材 (JavaFX是Java语言的下一代…

Flutter系列(十二)实现购物车和提交订单页

基础工程: Flutter系列(十一)实现商城首页和商品详情页_摸金青年v的博客-CSDN博客 Flutter系列(四)底部导航顶部导航图文列表完整代码_摸金青年v的博客-CSDN博客 一、前言 本文用flutter实现购物车和提交订单页&#x…

【裸机开发】认识中断向量表(设置中断向量偏移的原因)

之前的LED驱动不存在中断,也就不包含中断的初始化。如果程序包含了中断,我们应还需要初始化哪些内容?要解决这个问题,我们需要先了解一个中断系统包含了哪些内容。 ① 中断向量表:描述中断对应的中断服务函数&#xf…

数据结构——栈的实现(动态增长版本)

堆栈(stack)又称为栈或堆叠,是计算机科学中的一种抽象资料类型,只允许在有序的线性资料集合的一端(称为堆栈顶端,top)进行加入数据(push)和移除数据(pop&…

【软件架构】流水线设计模式

流水线模式 流水线模式是一种软件设计模式&#xff0c;它提供了构建和执行一系列操作的能力。 此模式最好与插件模式结合使用&#xff0c;以便在应用程序启动时动态构建流水线。 顺序 流水线的最基本实现是一个简单的操作序列。 public interface IOperation<T>{void …

【学习笔记】关于transformer

1.Embedding 一文读懂Embedding的概念&#xff0c;以及它和深度学习的关系 - 知乎 one-hot编码当矩阵过于稀疏时计算开销大&#xff0c;于是加上Embedding层&#xff0c;通过Embedding层&#xff08;矩阵乘法&#xff09;实现降维。 Embedding层将一个一个词&#xff08;词源…

Spring Boot 如何使用 Spring Security 进行认证和授权

Spring Boot 如何使用 Spring Security 进行认证和授权 在 Web 应用程序中&#xff0c;认证和授权是非常重要的功能。Spring Security 是一个基于 Spring 框架的强大的安全框架&#xff0c;它提供了完整的认证和授权解决方案&#xff0c;并且可以轻松地集成到 Spring Boot 应用…

gtk_table_attch与gtk_grid_attach的区别

gtk_table_attch与gtk_grid_attach的区别 button gtk_button_new_with_label (“Short fat button”); gtk_table_attach (GTK_TABLE (table), button, 0, 2, 3, 4, xoptions, yoptions, 0, 0); 0—2–3—4 左 右 上 下 /* 横线从左边的0移到右边的2&#xff0c;竖线从上边的…

3 python进阶篇

文章目录 面向对象类属性和类方法类属性类方法静态方法 单例模式__new__ 方法类实现单例模式 异常 、模块和包异常自定义异常 模块和包模块的搜索顺序包的init文件发布模块&#xff08;了解&#xff09; 文件seek文件/目录的常用管理操作eval函数 补充性知识位运算小技巧 参考我…

软考A计划-系统集成项目管理工程师-一般补充知识-中

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例点击跳转>软考全系列 &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff…

【LeetCode热题100】打卡第27天:二叉树的前序、中序、后序遍历

文章目录 【LeetCode热题100】打卡第27天&#xff1a;二叉树的前序、中序、后序遍历⛅前言&#x1f4d5;二叉树的前序遍历&#x1f512;题目&#x1f511;题解 &#x1f4d5;二叉树的中序遍历&#x1f512;题目&#x1f511;题解 &#x1f4d5;二叉树的后序遍历&#x1f512;题…

(万字长文)React 18 源码与原理解读 —— 看这一篇就够了

写在专栏开头&#xff08;叠甲&#xff09; 作者并不是前端技术专家&#xff0c;也只是一名喜欢学习新东西的前端技术小白&#xff0c;想要学习源码只是为了应付急转直下的前端行情和找工作的需要&#xff0c;这篇专栏是作者学习的过程中自己的思考和体会&#xff0c;也有很多参…

django中模板的使用

django中模板的使用 第一步 创建模板文件夹第二步 把模板存放进去第三步 把模板路径 加入到setting.py第四步 在视图函数处理第五步 路由挂载第六步 网页访问 第一步 创建模板文件夹 在项目的同层级下 新建模板文件夹 第二步 把模板存放进去 index.html <!DOCTYPE html&…

【Docker】一文了解Docker

文章目录 什么是Docker?为什么要使用Docker&#xff1f;与虚拟机的比较Docker架构Docker使用场景Docker安装阿里云镜像加速器1、登录阿里云2、找到镜像加速器3、配置使用 如今Docker的使用已经非常普遍&#xff0c;特别在一线互联网公司。使用Docker技术可以帮助企业快速水平扩…

C++ 自己动手实现简单的文件操作 (2023.6.23)

C 自己动手实现简单的文件操作 2023.6.23 引言1、文件简介2、各式各样的文件格式2.1 不同类型文件的扩展名2.1.1 文本文件2.1.2 数据文件2.1.3 音频文件2.1.4 视频文件2.1.5 电子书文件2.1.6 3D图像文件2.1.7 位图文件2.1.8 矢量图文件2.1.9 相机原始文件2.1.10 页面布局文件2.…

自监督对比学习框架SimCLR原理

目录 一、前言 人工智能发展近况 对比学习 二、数据集介绍 STL-10数据集 三、无监督图像表征对比学习 SimCLR SimCLR算法基本原理 数据增强与正负样本匹配 编码器 损失函数 对比学习全过程 四、有监督的图像下游任务迁移 替换下游任务网络层 有监督训练 五、实…

环境配置 | Git的安装及配置[图文详情]

Git是一个开源的分布式版本控制系统&#xff0c;可以有效、高速地处理从小到大的项目版本管理。下面介绍了基础概念及详细的用图文形式介绍一下git安装过程. 目录 1.Git基础概念 2.Git的下载及安装 3.常见的git命令 Git高级技巧 Git与团队协作 1.Git基础概念 仓库&#…

Charm-Crypto在Anaconda虚拟环境下的安装教程--基于Ubuntu20.04

第零步 VMware虚拟机设置和安装Anaconda虚拟环境 因为后面要编译源码&#xff0c;所以最好把CPU设置为最大&#xff0c;例如我的电脑是4核8线程&#xff0c;则&#xff1a; 关于Anaconda虚拟环境&#xff0c;这里不再赘述&#xff0c;后面都假设已经安装好虚拟环境&#xff0c;…