当前位置: 首页 > news >正文

学习系统编程No.35【基于信号量的CP问题】

引言:

北京时间:2023/8/2/12:52,时间飞逝,恍惚间已经来到了八月,给我的第一感觉就是快开学了,别的感觉其实没有,哈哈!看着身边的好友网络相关知识都要全部学完了,就好像它们工业革命都要完成了,而我还在刀耕火种,哈哈哈!他们在高速发展阶段,我还在休养生息,哈哈哈!不怕,正所谓没有压力没有动力,让压力来的再猛烈一些吧!咱无所畏惧,八月就是咱逆分翻盘之月,舍我其谁,冲冲冲!这句话我好想在哪见过,哈哈哈!别笑,我真的在很严肃的看待这件事。八月没有什么拓展内容,本来还想着在暑假的时候提高一下自己的做题能力,可惜,现在连课都学不明白,可能是当时无知以为自己时间很多,也可能确实是我们自己没把握好,反正现在不管那么多,八月我们的目标就是将网络搞定。废话不多说,进军网络之前,我们还是先把有关系统方面的知识先给搞定,今天就让我来看看基于信号量知识的生产消费模型吧!

在这里插入图片描述

基于环形队列的CP问题

上篇博客由于时间原因,这部分有关信号量接口实操的知识我们没有讲解,来到该篇博客,我们在重温生产消费模型的基础上,来看看如何实现信号量对共享资源中资源数量控制的同时,让多线程可以安全的访问共享资源吧!

1.复习环形队列

为什么使用环形队列?
在学习信号量时,我们重点强调了为什么要学习信号量,并且明白信号量分为二元信号量(互斥锁)和多元信号量,信号量就是一个对共享资源中资源数量的预定机制,结合当时我们没有使用信号量实现的生产消费模型我们可以发现,使用和不使用信号量的区别在于共享资源中是否存在多个临界资源需要被控制。在之前学习有关BlockQueue队列的CP模型我们可以发现,它并不存在多个临界资源,所以我们是直接采用互斥锁和条件变量的形式对其进行同步机制控制,也就是对那唯一的共享资源进行保护就行。而当我们今天想要实现一份有信号量知识的CP模型,那么前提就是需要有多个临界资源,而我们的环形队列就能很好的实现这一效果。

回顾环形队列相关的知识
明白了为什么要回顾环形队列之后,此时我们正式来复习一下环形队列,毕竟这块知识已经学习快一年了(去年11月份学的),并且当时出处茅庐,虽然也写了博客,但是当时的博客并没有非常用心,所以这一块知识给我的感觉比较模糊,该篇博客我们就来重点复习一下,由于这部分知识属于初阶数据结构,并不适合展开理解,也就是相关代码实现我们不体现出来,感兴趣的小伙伴可以参考我之前的代码实现,所以这里我们重点复习一下有关环形队列的概念知识,也就是环形队列的实现原理和特征。 我们都知道无论学习什么都是由浅入深,由概念到实操,如果一上来就是实操,那么你只会感觉天昏地暗,什么都听不懂,好比之前我们在学习生产消费模型,每个人的实操代码都有可能是不同的,但是其中的原理生产者和消费者的三种关系是谁都不能改变的,所有人都必须紧紧围绕这个原理来实现自己的CP模型。所以同理在数据结构中,无论是那种数据结构,它们本质的不同就在于设计原理不同,所以你想要看懂一份某数据结构的代码,前提就是你对该数据结构的设计原理了如指掌,同理你想实现一份代码,也是这个道理。现在就让我们来看看环形队列是由那些原理组成的吧!

环形队列原理和概念
环形队列也叫循环队列,本质是通过一个数组或者链表实现,同理只要按照环形队列的特征来实现,无论使用数组还是链表都没有太大区别,最本质的区别还要归咎于数组和链表的区别,也就是数组和链表各自的优缺点(这里我不展开复习),所以我们只要明白环形队列就是一个固定大小但可以重复使用的队列,其最大的特征在于开辟空间存储数据时,要多开辟一个空间(无论是数组还是链表实现),该空间用于解决环形队列最大的问题:判空判满问题,也就意味着我们在实现环形队列时,用户如果想要一个可以存储 K(10)个数据的环形队列,那么在环形队列内部new空间时就需要new K+1个空间,当然这一过程由于被封装的原因,上层用户并不能体会到,但是你下层代码为了实现这一功能,你就必须这么做(同理很多不看源码你无法理解的功能都是通过这种理念实现)。所以我们根据这一特征,此时就能明白如果环形队列是通过数组实现,那么此时它就需要通过两个整形数据front和tail作为数组下标来控制队列的循环过程,当(tail+1) % (K+1) = front时,就表示此时环形队列满了,而如果我们的环形队列是通过链表来实现,那么此时它就需要通过两个结点指针来控制队列的循环过程,当tail->next = front时表示环形队列满了。明白了这些知识之后,对于环形队列的判空判满简直不要太简单,明白了环形队列的判空判满对于环形队列剩余的知识当然也不要太简单,总而言之: 环形队列没什么重点,重点就在于理解环形队列为什么叫做环形队列,也就是如何实现对空间的循环使用。 取模运算(a % b)可用于获取到一个0~b-1之间的数,常用于对数组越界和数组下标的控制。

在这里插入图片描述

2.正式进入CP问题

明白了上述有关环形队列知识的学习,此时顺理成章正式进入有关信号量实操问题,也就是我们一直说的使用信号量来控制生产消费模型的实现,所以接下来我们就来看看,如何让信号量控制环形队列中的临界资源吧!

同理,实操的前提是对相关知识概念非常清晰,首先我们就来谈谈在环形队列中信号量是如何控制资源以及使用信号量实现生产消费模型的特征,也就是基于信号量的生产消费模型的实现原理。此时我们明白,对于生产者和消费者来说它们关注的资源是不一样的,生产者关注的是环形队列,也就是共享资源中的空间资源,而消费者关心的则是共享资源中的数据资源,也就是当我们把一整块共享资源划分为一份一份的临界资源时,此时生产者和消费者之间关心的资源不同,生产者关心的是该共享资源中可写入数据的空间数量,而消费者关心的是生产者写入数据之后产生的数据数量。所以当我们有了这两个抓手之后,信号量实现生产消费模型的问题我们就差不多搞定了,因为我们已经将该生产消费模型中的信号量给找到了,也就是将共享资源中的空间资源看做是生产者的信号量,而共享资源中的数据资源看做是消费者的信号量,结合之前学习的有关信号量的知识,此时我们就知道,只有当共享资源中还有未存储数据的空间,此时才允许某个生产者线程去申请空间信号量(调度器决定),当获取到了空间信号量之后才能继续向后执行,反之等待。只有当共享资源在生产者生产之后,也就是共享资源中有数据资源时,才允许某个消费者线程去申请对应的数据资源,同理获取之后才允许向后执行。当然具体信号量是如何实现没有对应信号量资源时,让线程发生等待,这个由底层代码决定,这里我们不关心,同理我们只关心信号量的使用规则和相应的功能。

如何实现信号量的P/V操作
明白了上述对信号量实现生产消费模型的简易分析,此时我们就知道,在该生产消费模型中存在两个信号量,一个是生产者的空间信号量,一个是消费者的数据信号量,那么此时问题又来了,我们应该如何对这两个信号量进行控制呢?当然对于信号量的控制本质就是使用我们上篇博客中谈到的sem_post接口和sem_wait接口,也就是伴随信号量产生的P/V操作。首先,肯定是要有两个不同的信号量来分别表示空间信号量和数据信号量,当我们在实现代码时我们就可以定义为_space_sem和_data_sem,然后通过信号量初始化接口sem_init进行初始化,并且因为空间资源先天存在,数据资源后天由生产者生产,所以我们将_space_sem初始化为num(环形队列大小决定),_data_sem初始化为0。然后,当多个生产者线程和消费者线程开始同时访问共享资源时,因为空间信号量存在,数据信号量不存在,所以此时生产者线程可以分配到信号量(调度器决定)且对应空间信号量发生P(_space_sem)操作,直到_space_sem数量为0,生产者线程全部等待,而消费者线程由于数据信号量天生不存在,所以在生产者进行V(_data_sem)操作之前,全部等待,只有当_data_sem数量不为0,此时消费者线程才可能分配到信号量且对应数据信号量发生P(_data_sem)操作,完成消费之后同理进行V(_space_sem)操作,将空间资源释放。从而如此以往,循环往复的实现生产和消费。

注意: 生产消费模型并不代表只有当生产者线程生产完成之后,消费者线程才能消费,从之前学习有关BlockQueue模型的生产消费模型我们也能看出,无论是生产者还是消费者只要其抢到了锁并且符合条件变量,那么它就可以执行,只不过对于BlockQueue来说,我们是使用同一把锁的形式来控制生产者和消费者之间的同步关系,从而实现共享资源只能被生产者或者消费者其中一个线程执行,而此时对于CircleQueue模型来说,我们支持生产者和消费者同时访问共享资源,但不支持它们同时访问该共享资源中的同一份临界资源,从而实现同步关系。那么此时这个同步关系主要体现在哪里呢?信号量会给你答案,我们通过控制两个不同信号量之间的P/V操作来实现这一同步机制,从空间信号量产生数据信号量这一个原理我们可以很好理解,也就是只有当进行了生产者生产,数据信号量才会增加,消费者才能消费这一原理,我们能很好的体会到生产者和消费者之间的同步关系。

3.完整代码实现

3.1 单生产单消费

在这里插入图片描述

但注意: 上述代码只有在单生产单消费的场景下才能运行,也就是如果想要在多生产多消费的场景下运行,此时我们就要让多线程之间发生互斥,当然也就是通过互斥锁来实现,那么为什么呢?首先明白,虽然像上述这种类对象,本质所有线程在访问该类中的接口时,都会将该类的对象,也就是上述对象给拷贝一份到自己的栈空间,从而让该类中的接口看做是一个可重入函数接口,但是由于此时我们对类对象c_step和p_step(看成局部变量)存在修改数据的行为,所以此时线程之间就会因为竞态条件的发生,造成数据的不确定性,引发线程安全问题,因此我们就需要使用互斥的方式,来保护该类对象的安全。明白了这个原因之后,此时下述进行加锁之后的代码,就是我们要的多生产多消费代码,如下代码所示:

3.2 多生产多消费

在这里插入图片描述
最后明白,无论是单生产单消费,还是多生产多消费,本质并没有说谁的效率更高,并不是单纯的认为多生产多消费的效率就更高,如:想要实现多生产多消费就需要进行加锁操作,对于加锁肯定是会浪费效率的,并且由于多生产多消费,那么多线程之间的上下文切换也会造成一定的效率损失,所以具体使用多生产多消费,还是单生产单消费,是需要通过问题场景来决定的,同理,使用信号量和不使用信号量,也需要具体问题具体分析。总而言之:还是那句话,CP问题最大的好处不在于生产者和消费者之间的串行执行过程,而是在于线程间的解耦和并发问题,也就是之前说过的,线程在等待期间(无论是信号量、互斥锁)它可以访问其它资源(无论是否为共享资源),从而让系统内部的资源利用率提高,从而增强系统的响应速度。

总结:该篇博客有关信号量实操相关的知识,也就是基于信号量的CP问题我们就搞定啦!并且有关多线程相关的知识我们就要学完了,下篇博客应该就是我们对多线程知识的最后一篇博客,当然也就是有关系统编程相关知识的最后一篇博客,网络我来啦!See you!

相关文章:

学习系统编程No.35【基于信号量的CP问题】

引言: 北京时间:2023/8/2/12:52,时间飞逝,恍惚间已经来到了八月,给我的第一感觉就是快开学了,别的感觉其实没有,哈哈!看着身边的好友网络相关知识都要全部学完了,就好像…...

词嵌入、情感分类任务

目录 1.词嵌入(word embedding) 对单词使用one-hot编码的缺点是难以看出词与词之间的关系。 所以需要使用更加特征化的表示(featurized representation),如下图所示,我们可以得到每个词的向量表达。 假设…...

TypeScript使用技巧

文章目录 使用技巧TypeScript内置的工具类型keyofextends 限定泛型interface 与 type 区别 TypeScript作为JavaScript的超集,通过提供静态类型系统和对ES6新特性的支持,使JavaScript开发变得更加高效和可维护。掌握TypeScript的使用技巧,可以帮助我们更好地开发和组织JavaScrip…...

MySQL — InnoDB事务

文章目录 事务定义事务特性事务隔离级别READ UNCOMMITTEDREPEATABLE READREAD COMMITTEDSERIALIZABLE 事务存在的问题脏读(Dirty Read)不可重复读(Non-repeatable Read)幻读(Phantom Read) 事务定义 数据库…...

LeetCode 42. 接雨水(动态规划 / 单调栈)

题目: 链接:LeetCode 42. 接雨水 难度:困难 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。 示例 1: 输入:height [0,1,0,2,1,0,1,3,2,1,2…...

顺序表、链表刷题指南(力扣OJ)

目录 前言 题目一:删除有序数组中的重复项 思路: 题解: 题目二:合并两个有序数组 思路: 分析: 题解: 题目三:反转链表 思路: 分析: 题解: 题目四&…...

Lambda表达式总结

Lambda作为Java8的新特性,本篇文章主要想总结一下常用的一下用法和api 1.接口内默认方法实现 public interface Formula {double calculate(int a);// 默认方法default double sqrt(int a) {return Math.sqrt(a);} }public static void main(String[] args) {Form…...

岛屿的最大面积

给你一个大小为 m x n 的二进制矩阵 grid 。 岛屿 是由一些相邻的 1 (代表土地) 构成的组合,这里的「相邻」要求两个 1 必须在 水平或者竖直的四个方向上 相邻。你可以假设 grid 的四个边缘都被 0(代表水)包围着。 岛屿的面积是岛上值为 1 …...

迭代器模式(Iterator)

迭代器模式是一种行为设计模式,可以在不暴露底层实现(列表、栈或树等)的情况下,遍历一个聚合对象中所有的元素。 Iterator is a behavior design pattern that can traverse all elements of an aggregate object without exposing the internal imple…...

Goland搭建远程Linux开发

Windows和Linux都需要先构建好go环境,启用ssh服务。 打开Windows上的Goland,建立项目。 点击添加配置,选择go构建 点击运行于,选择ssh 填上Linux机器的IP地址和用户名 输入密码 没有问题 为了不让每次运行程序和调试程序都生…...

react中PureComponent的理解与使用

一、作用 它是一个纯组件,会做一个数据的浅比较,当props和state没改变的时候,不会render重新渲染, 改变后才会render重新渲染,提高性能。 二、使用 三、注意 它不能和shouldComponentUpdate生命周期同时使用。因为它…...

洛谷——P5714 【深基3.例7】肥胖问题

文章目录 题目题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1 样例 #2样例输入 #2样例输出 #2 提示 AC代码 题目 题目描述 BMI 指数是国际上常用的衡量人体胖瘦程度的一个标准,其算法是 m h 2 \dfrac{m}{h^2} h2m​,其中 m m m 是指体重&am…...

Mac隐藏和显示文件

由于之前没有使用过Mac本,所以很多地方都不太清楚,在下载git项目的时候,发现没有.git文件, 一开始还以为下载错了,但是git命令是可以看到远端分支以及当前分支的,之后在一次解压文件的时候发现,…...

软件工程中应用的几种图辨析

【软件工程】软件工程中应用的几种图辨析:系统流程图、数据流图、数据字典、实体联系图、状态转换图、层次方框图、Warnier图、IPO图、层次图、HIPO图、结构图、程序流程图、盒图、PAD图、判定表_眩晕李的博客-CSDN博客 软件工程——实体关系图 状态转换图 数据流…...

下载离线版的VS Visual Studio 并下载指定的版本

一、先下载引导程序 下载地址VS VisualStudio官网 在这个页面翻到最下面 在这里下载需要的版本 下载引导程序 二、下载离线安装包 写一个批处理文件&#xff08;vs.bat&#xff09; 命令格式如下 <vs引导程序exe> --layout <离线安装包下载的路径> --add <功能…...

Eureka 学习笔记5:InstanceRegistry

版本 awsVersion ‘1.11.277’ LeaseManager 接口管理实例的租约信息&#xff0c;提供以下功能&#xff1a; 注册实例取消注册实例实例续约剔除过期实例 public interface LeaseManager<T> {/** 注册实例并续约*/void register(T r, int leaseDuration, boolean isRep…...

System Verilog——虚方法的使用

1、使用虚方法目的 通过在父类里定义虚方法(task or function)&#xff0c;可以在当父类句柄调用一个方法时候&#xff0c;前提是若是这个句柄指向了子类对象&#xff0c;则调用的方法为子类的方法而不是父类的方法。 1.1、实例理解&#xff1a;将子类句柄赋值成父类句柄 mod…...

线性规划和单纯形法-原理篇

文章目录 引言线性规划标准型问题特点单纯形法 引言 很多运筹学的教材都是从线性规划开始的&#xff0c;我平时做算法策略的落地应用时也研发了一部分基于线性规划的技术方案。可以说&#xff0c;如果搞不懂线性规划&#xff0c;很难成为一名优秀的运筹优化算法工程师。 但是…...

FBX SDK开发快速上手指南

一段时间以来&#xff0c;我一直想制作一个 FBX Exporter 将 FBX 文件转换为我自己的格式。 整个过程不是很顺利&#xff0c;主要是FBX的官方文档不是很清楚。 另外&#xff0c;由于 FBX 格式被许多应用程序使用&#xff0c;而不仅仅是游戏引擎&#xff0c;因此提供的示例代码没…...

探讨|使用或不使用机器学习

动动发财的小手&#xff0c;点个赞吧&#xff01; 机器学习擅长解决某些复杂问题&#xff0c;通常涉及特征和结果之间的困难关系&#xff0c;这些关系不能轻易地硬编码为启发式或 if-else 语句。然而&#xff0c;在决定 ML 是否是当前给定问题的良好解决方案时&#xff0c;有一…...

vscode里如何用git

打开vs终端执行如下&#xff1a; 1 初始化 Git 仓库&#xff08;如果尚未初始化&#xff09; git init 2 添加文件到 Git 仓库 git add . 3 使用 git commit 命令来提交你的更改。确保在提交时加上一个有用的消息。 git commit -m "备注信息" 4 …...

C++_核心编程_多态案例二-制作饮品

#include <iostream> #include <string> using namespace std;/*制作饮品的大致流程为&#xff1a;煮水 - 冲泡 - 倒入杯中 - 加入辅料 利用多态技术实现本案例&#xff0c;提供抽象制作饮品基类&#xff0c;提供子类制作咖啡和茶叶*//*基类*/ class AbstractDr…...

【OSG学习笔记】Day 18: 碰撞检测与物理交互

物理引擎&#xff08;Physics Engine&#xff09; 物理引擎 是一种通过计算机模拟物理规律&#xff08;如力学、碰撞、重力、流体动力学等&#xff09;的软件工具或库。 它的核心目标是在虚拟环境中逼真地模拟物体的运动和交互&#xff0c;广泛应用于 游戏开发、动画制作、虚…...

3.3.1_1 检错编码(奇偶校验码)

从这节课开始&#xff0c;我们会探讨数据链路层的差错控制功能&#xff0c;差错控制功能的主要目标是要发现并且解决一个帧内部的位错误&#xff0c;我们需要使用特殊的编码技术去发现帧内部的位错误&#xff0c;当我们发现位错误之后&#xff0c;通常来说有两种解决方案。第一…...

关于iview组件中使用 table , 绑定序号分页后序号从1开始的解决方案

问题描述&#xff1a;iview使用table 中type: "index",分页之后 &#xff0c;索引还是从1开始&#xff0c;试过绑定后台返回数据的id, 这种方法可行&#xff0c;就是后台返回数据的每个页面id都不完全是按照从1开始的升序&#xff0c;因此百度了下&#xff0c;找到了…...

【算法训练营Day07】字符串part1

文章目录 反转字符串反转字符串II替换数字 反转字符串 题目链接&#xff1a;344. 反转字符串 双指针法&#xff0c;两个指针的元素直接调转即可 class Solution {public void reverseString(char[] s) {int head 0;int end s.length - 1;while(head < end) {char temp …...

视频字幕质量评估的大规模细粒度基准

大家读完觉得有帮助记得关注和点赞&#xff01;&#xff01;&#xff01; 摘要 视频字幕在文本到视频生成任务中起着至关重要的作用&#xff0c;因为它们的质量直接影响所生成视频的语义连贯性和视觉保真度。尽管大型视觉-语言模型&#xff08;VLMs&#xff09;在字幕生成方面…...

3403. 从盒子中找出字典序最大的字符串 I

3403. 从盒子中找出字典序最大的字符串 I 题目链接&#xff1a;3403. 从盒子中找出字典序最大的字符串 I 代码如下&#xff1a; class Solution { public:string answerString(string word, int numFriends) {if (numFriends 1) {return word;}string res;for (int i 0;i &…...

重启Eureka集群中的节点,对已经注册的服务有什么影响

先看答案&#xff0c;如果正确地操作&#xff0c;重启Eureka集群中的节点&#xff0c;对已经注册的服务影响非常小&#xff0c;甚至可以做到无感知。 但如果操作不当&#xff0c;可能会引发短暂的服务发现问题。 下面我们从Eureka的核心工作原理来详细分析这个问题。 Eureka的…...

Vite中定义@软链接

在webpack中可以直接通过符号表示src路径&#xff0c;但是vite中默认不可以。 如何实现&#xff1a; vite中提供了resolve.alias&#xff1a;通过别名在指向一个具体的路径 在vite.config.js中 import { join } from pathexport default defineConfig({plugins: [vue()],//…...