信号量——生产消费者模型

       前文

        在这一篇博客(信号量博客)中我曾经提及过信号量的知识,而当对信号量进行提炼总结时,大致是以下三点:

        1. 信号量本质是一个计数器(代表资源的数量)

        2. 申请信号量本质就是对资源的一种预定机制

        3. 信号量的PV操作是原子的

        我也在这篇博客(生产消费者模型的实现博客)中介绍过生产消费者模型,这种模型使用了多执行流以生产者负责接受并派发任务,消费者负责执行生产者派发的任务,这种方式能够一定程度上提高代码的执行效率,其中的实现方式根据消费者与消费者之间、生产者与生产者之间、还有消费者与生产者之间的互斥同步关系完成的。

        在本文章中我将对信号量进行较深入的介绍,以及使用信号量重新实现一份新的生产消费者模型。

        信号量

        在实现生产消费者模型博客的过程中,我使用的是阻塞队列来作为一种生产消费模型的基础加以实现的。在这其中,阻塞队列一直是被当作一个整体的资源来被访问,即因为生产者和消费者彼此间互斥,所以同一时刻只能有一个执行流访问阻塞队列,但是我们知道对于队列来说头出尾进看着是生产者生产数据并不会影响消费者消费数据的过程,但其实对于C++中的STL容器来说,容器有可能随时扩容,所以队列本身是线程不安全的,也需要互斥。

        那基于这样的想法,我们可不可以自己开辟一段空间,就将这段空间分成多个块,每个块都是一个基本资源,而每个线程都独自访问属于自己的块资源,那么这么来看的话,这种访问方式本身就是线程安全的不需要使用互斥来控制(因为本质上没有共享资源)。而现在资源的控制就可以转化为使用信号量来表示这段空间中有多少个数据块是可用的,然后当线程需要资源的时候,先申请信号量,然后通过一定操作访问到此时属于自己的资源进行处理,然后释放信号量

        我们在多执行流并发访问共享资源时,都是通过加锁的方式保证线程安全,然而当执行流进入临界区访问共享资源时,一般还要确定该共享资源是否已经准备好了。

加锁之后仍需要判断资源是否已经准备好

        然而对于信号量所管理的资源,我们还需要在申请信号量成功之后再次确认资源是否已经准备好了吗?很明显是不用了,因为信号量的申请成功就意味着此时一定是有资源的。这是与单纯的互斥保护临界资源不同的一点。

        这样的话我们的共享资源好像因为信号量的存在变得能多执行流并发访问了一样。

        信号量的接口

        现在我来简单的介绍一下信号量的接口:

        首先是信号量的初始化函数,第一个参数就是信号量类型为sem_t,第二个参数是表示该信号量是线程内共享还是进程内共享(0是线程内共享),第三个参数就是资源的数量。

        这个是信号量的销毁函数。

        这个函数对应信号量的P操作,即申请信号量,当申请信号量失败时,就会被阻塞在对应的等待队列中。

        这个就是信号量的V操作,即释放信号量。

        生产消费模型

        现在我会介绍如何使用信号量来重新实现一个生产消费者模型,然后这里的生产消费者模型使用的数据机构是循环队列,并且刚开始是单生产单消费。

        我们来想象一下循环队列中的生产消费者模型是什么样的:

        我们知道,生产者负责传递数据,消费者负责接收数据,我们现在来假设这么一种极端情况:生产者一直在生产,而消费者不消费,那么当生产者再次碰到消费者时意味着队列中的数据已经满了,生产者已经不能再生产数据了,这也意味着环形队列中,生产者不能超过消费者一圈。

        而对于消费者而言,假如此时生产者已经将队列中填满数据,此时消费者开始消费数据,当消费者再次碰到生产者时,意味着队列中已经没有有效数据了,也意味着消费者不能超过生产者,要始终在消费者之后。

        我们知道,当线程启动时,操作系统对于执行流的调度是不确定的,但是对于上述模型来说,刚开始也就是队列中没有数据的时候,我们的消费者线程是不能运行的,是要被阻塞的。此时要等待生产者生产数据之后,消费者才能开始执行,而队列中充满数据时,生产者线程是要被阻塞的,直到消费者线程消费了数据之后,生产者线程才能开始执行。这段话中我们发现两种现象:那就是当队列中为空或为满的时候只能由生产者或者消费者访问共享资源。这其中,只能单一执行流访问共享资源体现了互斥,执行执行流访问体现了同步,所以这是需要注意的点。

        但是,除了以上两种极端情况外,惊奇的发现生产者线程和消费者线程是可以并发访问共享资源的,因为它们访问的是共享资源的不同位置。

        假如使用代码实现生产者生产和消费者消费的逻辑该怎么实现呢?

        

        有的人可能就有疑问了,为什么两个线程申请和释放的信号量不是一个而是交叉的啊?

        这其实不难理解,关于两个线程的申请各自的信号量很正常,而对于释放对方的信号量,当生产者生产数据之后,直观的能够看到数据资源增加了一份所以应该对数据信号量进行V操作,而当消费者线程消费数据之后,那个数据的空间就闲置了,空间资源增加了一份所以应该是对空间信号量进行V操作。

        而对于“通过一定操作访问到此时属于自己的资源进行处理” 这个步骤,体现在环形队列中就是,每个元素的位置了,假如环形队列是一个数组的话那就是数组的下标了。由于生产者和消费者在大部分情况下是并行访问队列的,所以生产者和消费者应该有着自己的下标管理。

        关于更多的细节,我们需要在代码中体现了。

        CP模型的实现

现在我们就来简单实现一下,CP模型:

        大致框架与阻塞队列的CP模型一致,但是我们这里使用的不再是互斥量和条件变量,而是信号量,所以我们现在就应该思考一下应该有几个信号量呢?在这里由于生产者和消费者对于“资源”的认识是不同的,生产者所认识的“资源”是队列中的空间,而消费者认识中的“资源”是数据,所以这里我们应该使用两个信号量来表示资源,而对于空间信号量来说初始值应该是队列的大小,资源信号量的初始值是0:

        接下来是生产者生产数据和消费者消费数据的具体逻辑:

至此我们的生产消费者模型就完成了,以下是测试代码:

        在CP模型中还是老生常谈,它不止能传输整形,还能传输任意类型的对象,因为我们使用了模板。

        并且我们发现信号量它很好的作为循环队列的一部分特性而存在,如果没有信号量的话,我们还需要考虑循环队列中是空的还是满的,这需要我们使用额外变量或者空出队列中一个元素格子的代价来实现。

        除此之外我们发现这样由信号量实现的CP模型更加的高效,因为它允许生产者和消费者在大部分时刻下并发访问共享资源。

        多生产消费

        现在就该讨论一下,关于多生产多消费了。

        我们在阻塞队列中的由单生产单消费过渡到多生产多消费时,什么都不用做,因为保护的资源只有一个,而那一个资源已经被保护好了。而在这里,对于消费者和生产者而言它们俩之间的共享资源有信号量和队列,信号量是原子的,而队列又被信号量所保护着,所以这个关系可以还能好的维护,而对于消费者和消费者之间、生产者和生产者之间,它们的共享资源除了原子性的信号量和队列外,还有一个指向队列下标的变量_p_index、_c_index,这两个变量如果不加以保护的话,由会产生数据不安全的问题。所以我们还需要两个锁来保护这两个不同的共享资源。我曾经提过锁的数量一般是与共享资源的而数量挂钩的:

头文件SmartMutex.hpp:

        加入了互斥锁之后,就又出现问题了:到底是先加锁再申请信号量呢,还是先申请信号量再加锁呢?

        当先加锁时,以Push函数为例,多个线程进入Push函数之后,首先要竞争锁资源,竞争到锁资源的线程再进行信号量的申请。

        而先申请信号量时,多个线程先是申请信号量,我们要知道信号量可是有可能有多个的,所以存在多个线程申请信号量成功,然后这些线程只需要竞争锁资源就可以了。

        前者的多个线程是先竞争后单个申请信号量,后者的多个线程前半部分申请信号量可能会存在一步到位的情况,后者再全部进行锁的竞争,竞争到之后直接开始资源的访问。

        这么一看明显是先申请信号量再加锁比较好,所以:

        这样我们就可以实现多生产多消费的CP模型了:

这里我防止打印错误,对打印的过程加了个锁。

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

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

相关文章

AI大模型额外学习一:斯坦福AI西部世界小镇笔记(包括部署和源码分析)

文章目录 一、简单介绍1)项目代码介绍2)重新播放模拟3)适当修改分叉模拟 二、部署斯坦福小镇Demo1)准备工作2)解决遇到的bug3)启动服务器和前端 三、源码剖析1)主题顺序 github链接 一、简单介…

luceda ipkiss教程 62:等长波导布线(二)

教程 27介绍了两段波导等长布线的例子,下面同样是通过控制偏移量实现三段波导的等长布线: 所有代码如下: from si_fab import all as pdk from ipkiss3 import all as i3class demo(i3.Circuit):mmi i3.ChildCellProperty(doc"mmi in…

【面经八股】搜广推方向:面试记录(九)

【面经&八股】搜广推方向:面试记录(九) 文章目录 【面经&八股】搜广推方向:面试记录(九)1. 自我介绍2. 科研-项目经历问答3. 实习经历问答4. 八股5. 编程题6. 反问1. 自我介绍 。。。。。。 2. 科研-项目经历问答 挑了我的论文,一直揪着问,建议一定要熟悉自…

mysql主从复制/主从备份搭建

mysql主从复制/主从备份搭建 前言一、主从复制1)为什么使用主从复制、读写分离?2)主从复制原理 二、如何实现主从复制?1)主库配置1、修改配置文件2、登录mysql: 2)从库配置1、修改配置文件2、登…

函数-Python

师从黑马程序员 函数初体验 str1"asdf" str2"qewrew" str3"rtyuio" def my_len(data):count0for i in data:count1print(f"字符串{data}的长度是{count}")my_len(str1) my_len(str2) my_len(str3) 函数的定义 函数的调用 函数名&a…

爱恩斯坦棋小游戏使用C语言+ege/easyx实现

目录 1、游戏介绍和规则 2、需要用到的头文件 3、这里我也配上一个ege和easyx的下载链接吧,应该下一个就可以 4、运行结果部分展示 5、需要用到的图片要放在代码同一文件夹下 6、代码地址(里面有需要用到的图片) 1、游戏介绍和规则 规则如…

电学基础知识

目录 电流 前言 电流的产生 电流的单位安培(A) 电路和电池 开路和闭路 电灯泡原理 对电池容量的理解 毫安时 毫瓦时 直流电和交流电 AC交流电 DC直流电 直流电和交流电对比 电压 对电器的电压和电流的理解 电阻 电压电阻电子的关系 欧…

GateWay路由规则

Spring Cloud GateWay 帮我们内置了很多 Predicates功能,实现了各种路由匹配规 则(通过 Header、请求参数等作为条件)匹配到对应的路由 1 时间点后匹配 server:port: 8888 spring:application:name: gateway-servicecloud:nacos:discovery:…

超实用!免费软件站大盘点,总有一款适合你

相信用Mac电脑的同学都知道一个网站MacWK,可以白嫖几乎所有常用软件,不用付费,但不好的消息是在2022年10月宣布关站,小编从此走上了开源免费的道路,尽管不太好用,奈何口袋木有钱,经过小编的不断…

VS2022 配置QT5.9.9

QT安装 下载地址:https://download.qt.io/archive/qt/ 下载安装后进行配置 无法运行 rc.exe 下载VS2022 官网下载 配置 1.扩展-管理扩展-下载Qt Visual Studio Tools 安装 2.安装完成后,打开vs2022,点击扩展,会发现多出了QT VS Tools,点…

【学习学习】学习金字塔

学习金字塔(Cone of Learning),全称学习吸收率金字塔,是一种现代学习方式的理论。网上流传它是美国缅因州的国家训练实验室(National Training Laboratories)研究成果,用数字形式形象显示了采用…

数据分析-Pandas序列时间移动窗口化操作

数据分析-Pandas序列时间移动窗口化操作 数据分析和处理中,难免会遇到各种数据,那么数据呈现怎样的规律呢?不管金融数据,风控数据,营销数据等等,莫不如此。如何通过图示展示数据的规律? 数据表…

Elasticsearch:调整近似 kNN 搜索

在我之前的文章 “Elasticsearch:调整搜索速度”,我详细地描述了如何调整正常的 BM25 的搜索速度。在今天的文章里,我们来进一步探讨如何提高近似 kNN 的搜索速度。希望对广大的向量搜索开发者有一些启示。 Elasticsearch 支持近似 k 最近邻…

MateBook D 14 SE版(2022版)使用体验

试用了一下华为的笔记本电脑,我觉得是出乎意料的好用,配置不算高,但是特别流程,而且自带了正版的office等软件,生产力杠杠的,外观也很不错,设计语言很高级,价格不贵,但是…

17.获取帖子列表

文章目录 一、建立帖子表结构并插入一些测试数据二、通过SQL建立对应的数据模型三、建立路由四、开发GetPostListHandler五、编写logic六、编写dao层七、编译测试运行 一、建立帖子表结构并插入一些测试数据 create table post (id bigint auto_increment primary k…

浅谈如何自我实现一个消息队列服务器(2)——细节详解

文章目录 一、实现 broker server 服务器1.1 创建一个SpringBoot项目1.2 创建Java类 二、硬盘持久化存储 broker server 里的数据2.1 数据库存储2.1.1 浅谈SQLiteMyBatis 2.1.2 如何使用SQLite 2.2 文件存储 三、将broker server 里的数据存储在内存上四、使用DataBaseManager类…

机器视觉引导的多材料3D打印

3D打印机使用机器视觉来解决困扰3D喷墨打印机的问题,增加了可以使用的材料范围,并实现了机器人手等复杂物体的快速生产。 增材制造(也称为 3D 打印)的进步已经产生了越来越强大的能力,可以生产使用传统制造工艺无法制…

IT系统可观测性

什么是可观测性 可观测性(Observability)是指能够从系统的外部输出推断出系统内部状态的能力。在IT和云计算领域,它涉及使用软件工具和实践来收集、关联和分析分布式应用程序以及运行这些应用程序的硬件和网络产生的性能数据流。这样做可以更…

QT----基于QT的人脸考勤系统

目录 1 编译opencv库1.1 下载源代码1.2 qt编译opencv1.3 执行Cmake一直卡着data: Download: face_landmark_model.dat 2 编译SeetaFace2代码2.1 遇到报错By not providing "FindOpenCV.cmake" in CMAKE_MODULE_PATH this project has2.2遇到报错Model missing 3 测试…

Git——GitHub远端协作详解

目录 Git&GitHub1、将内容Push到GitHub上1.1、在GitHub上创建新项目1.2、upstream1.3、如果不想要相同的分支名称 2、Pull下载更新2.1、Fetch指令2.2、Fetch原理2.3、Pull指令2.4、PullRebase 3、为什么有时候推不上去3.1、问题复现3.2、解决方案一:先拉再推3.3…