【Linux】-同步互斥的另一种办法-信号量

在这里插入图片描述
💖作者:小树苗渴望变成参天大树🎈
🎉作者宣言:认真写好每一篇博客💤
🎊作者gitee:gitee✨
💞作者专栏:C语言,数据结构初阶,Linux,C++ 动态规划算法🎄
如 果 你 喜 欢 作 者 的 文 章 ,就 给 作 者 点 点 关 注 吧!

文章目录

  • 前言
  • 一、信号量的概念
    • 二、POSIX信号量
  • 三、总结


前言

今天我们来讲解一下信号量,相比较之前学习的多线程中的互斥锁来说,信号量的概念比互斥锁要难理解,但是博主会使用生活中的例子,来给大家讲解,最后会得出互斥锁其实是信号量的一种特殊情况,但不是信号量,而理解的本质却是一样的,最后博主会使用代码给大家演示信号量是怎么实现多线程的同步互斥的


一、信号量的概念

为什么会有信号量,他也是解决我们多线程访问共享资源的安全问题,之前的锁已经帮助我们解决了这个问题,那为什么还需要信号量呢??计算机中任何一种东西的产生都有他适用的场景,所以信号量的产生是为了适用于其他场景的。我们的锁他的作用是将一整块共享资源都保护起来,让一个执行流去访问,万一这个执行流只访问这块共享资源的一部分,那么就没有必要把整块都保护起来,只需要保护他要访问共享资源的一部分就可以了,所以我们就可以将这块共享资源分成许多份,而信号量就是表示分成多少份的数量信号量(信号灯)的本质就是一把计算器,类似于int cnt=n(不是等于),用于描述临界资源数量的多少。

讲一个故事:

作为大学生,常见的娱乐就是看电影,电影院会在电影开始前,会卖票(票就是我们所以人的共享资源),假设电影院有100个座位,那么商家就会放出100张不重复的票(信号量就是100,表示临界资源有100个),他不会放多,也不会放少。此时我们就可以得出下面三个特点:

  1. 当我们看电影的时候,我们还没有去电影院,先买票–买票的本质就是对资源的预定机制(就是申请信号量的过程)
  2. 当我们买了一张票的时候,计数器就会减1,资源就是少1
  3. 当计数器到0之后,资源已经被申请完毕了

在这里插入图片描述

相比较之前的锁是对整个临界资源进行保护,而信号量则是对整体里面的一个小块进行预订,当多个执行流过来访问这个临界资源,必须先申请信号量,申请成功了这块资源就是自己的了,所以此时我们最怕的是:1.多个执行流访问一个小块的资源。2.n个资源,但有N+条执行流的时候必然会造成前面一点,所以信号量的作用机来了,信号量的数目就是资源数量,而执行流想要访问资源就必须申请信号量成功,此时信号量就会减1,当你使用资源的时候,就要释放信号量,此时信号量加1,这也是信号量的工作机制,也可以很好的解决第二点,对于第一点,如果有多个执行流来访问同一个资源,就可以把这个小块当成整体,在这个小块的临界资源上加锁,来实现互斥,对于这一整块临界资源,不就可以用一个剩余票数信号量表示,也可以设置一个卖出票数信号量表示,这样两个信号量自己申请信号量,释放对方的信号量,这个一会在案例当中会非常明显。 通过上面描述得出下面四点结论:

  1. 申请计数器成功,就表示我具有访问资源的权限了,就好比买到票了,座位就是我的,不管我去不去。
  2. 申请了计数器资源,我当前访问我要的资源了吗??没有。申请了计数器资源就是对资源的余地给机制。
  3. 计数器可以有效保证进入共享资源的执行流数量
  4. 所以每一个执行流,想要访问共享资源的一部分的时候,不是直接访问,而是先申请计数器资源

在此理解:
如果我们电影院里面只有一个座位呢??说明我们的临界资源数目就不是之前的100,而是1,所以信号量的数目就是1,只有一个人能抢到票,只有一个人可以看电影,也就是看电影期间只有一个执行流在访问临界资源,这个在之前说过的,当临界资源只能有一个执行流去访问这不就是之前说的线程互斥嘛。我们把信号量的值只能为1,0两态的计数器叫做二元信号量----本质就是锁
所以博主一开始说什么锁其实是信号量的一个特殊情况,但不是一个动心,本质理解是一样的。

其实让资源为1,这是程序员规定的,假设资源有100份,你也不可能设置成90份,也不可能设置多,设置为1,表示这块资源只能分成一份,不要分成多份,而是当成一个整体,整体申请,整体释放----整体加锁,整体解锁。

思考一下:
想要访问临界资源的前提是先申请信号量计数器资源,那么信号量计数器不也是共享资源嘛??当我们申请或者释放信号量的时候,要进行–和++,这个操作在多线程知识张杰说过不是原子的,所以在多线程的时候,可能在某一条汇编的时候就别切换走了,导致申请信号量出现问题。

申请信号量,本质是对计数器–,叫做P操作
释放信号量,本质是对计数器++,叫做V操作
正常++和–都不是原子性的,但是PV操作必须是原子的,只有一条汇编才是原子的(要么做,要么不做,没有正在做)

概念的总结:

  1. 信号量本质是一把计数器,PV操作,原子的。
  2. 执行流申请资源,必须先申请信号量资源,得到信号量之后,才能访问临界资源
  3. 信号量值1,0两态的叫做二元信号量,就是互斥锁功能
  4. 申请信号量的本质:是对临界资源的预定机制。

二、POSIX信号量

上面做了很多的铺垫,接下来我们要实现就是把之前的CP模型改成用信号量去实现的模式,我们之前的cp模型是基于阻塞队列去实现的,而现在我们是基于环形队列的cp模型,因为我们将一个整体分成许多块了,所以整个临界资源就不止一个线程在访问,所以需要下标来判断生产者和消费者线程的位置,从而使用下标的方式让他们不会访问同一个小块。之前我们学习过的环形队列,其实就是使用数组,然后对下标进行模运算,间接实现环形的效果。

在这里插入图片描述
来看代码实现,在CP模型的时候,写了一个任务计算器,当时没有演示,现在拿来用用:
RingQueue.hpp:

#include<iostream>
#include<semaphore.h>
#include<vector>
using namespace std;template<class T>
class RingQueue
{public:  RingQueue(int cap):cap_(cap),rq_(cap){sem_init(&p_sem_,0,cap);//生产者一开始的信号量数目是capsem_init(&c_sem_,0,0);//消费者一开始的信号量数目是0pthread_mutex_init(&lock_,NULL);//给锁进行初始化。p_index_=0;c_index_=0;}void P(sem_t *sem){sem_wait(sem);}void V(sem_t *sem){sem_post(sem);}void lock(pthread_mutex_t *mutex){pthread_mutex_lock(mutex);}void unlock(pthread_mutex_t *mutex){pthread_mutex_unlock(mutex);}void push(T data){P(&p_sem_);lock(&lock_);rq_[p_index_]=data;p_index_++;p_index_=p_index_%cap_;unlock(&lock_);V(&c_sem_);}void pop(T *data){P(&c_sem_);lock(&lock_);*data=rq_[c_index_];c_index_++;c_index_=c_index_%cap_;unlock(&lock_);V(&p_sem_);}~RingQueue(){sem_destroy(&p_sem_);sem_destroy(&c_sem_);pthread_mutex_destroy(&lock_);}
private:vector<T> rq_;sem_t p_sem_;sem_t c_sem_;int p_index_;//生产者下标用来表示生产者到哪一块资源了int c_index_;pthread_mutex_t lock_;int cap_;//表示环形队列的最大长度
};

main.cc:

#include"RingQueue.hpp"
#include"task.hpp"
#include<string>
#include<unistd.h>
#include<ctime>
template<class T>
struct RQInfo
{RingQueue<T>* rq;string name;
};
void *producer_fun(void *arg)
{RQInfo<Task>* rq = static_cast<RQInfo<Task>*>(arg);string name = rq->name;RingQueue<Task>* rq_ptr = rq->rq;while(true){//生产任务int x=rand()%10+1;int y=rand()%10;char op=opers[rand()%opers.size()];Task t(x,y,op);rq_ptr->push(t);cout<<name<<" produce a task: "<<t.GetTask()<<endl;sleep(1);}return nullptr;
}void *consumer_fun(void *arg)
{RQInfo<Task>* rq = static_cast<RQInfo<Task>*>(arg);string name = rq->name;RingQueue<Task>* rq_ptr = rq->rq;while(true){Task t;rq_ptr->pop(&t);t();cout<<name<<" consume a task: "<<t.GetResult()<<endl;}return nullptr;
}
int main()
{srand(time(NULL));pthread_t producer[3],consumer[3];RQInfo<Task>* rq_info=new RQInfo<Task>();RingQueue<Task> *rq = new RingQueue<Task>(10);rq_info->rq = rq;//单生产单消费的环形CP模型。for(int i=0;i<1;i++){rq_info->name = "productor-"+to_string(i);pthread_create(producer+i,NULL,producer_fun,rq_info);sleep(1);}for(int i=0;i<1;i++){rq_info->name = "consumer-"+to_string(i);pthread_create(consumer+i,NULL,consumer_fun,rq_info);sleep(1);}for(auto tid:producer){pthread_join(tid,NULL);}for(auto tid:consumer){pthread_join(tid,NULL);}return 0;
}

测试办法:

  1. 先在main.c中将生产者消费者设置成单生产单消费,然后使生产者先休眠三秒,看看消费者什么状态,目的是测试生产者是不是先跑
    在这里插入图片描述
    代码设计细节:
    1.通过结构体将线程名和环形队列一起当参数传进去
    2.在设计一些接口时可以进行封装,我们信号量的借口和我们熟悉的PV操作不对应,封装成熟悉的
    3.我们进行加锁和申请信号量的时候,有两种一种先加锁,一种先申请,哪种好??答案是先申请好,(1)信号量本身就是原子的,不需要被保护。(2)先加锁在申请是一个串行,如果先申请在加锁,就会出现一个线程在访问的时候,其他线程可以申请信号量,实现并发访问。

三、总结

如果没有学习多线程,听信号量是一件痛苦的事情,但是我们学过多线程并且还学了锁,对于信号量的理解我认为大家是没有问题的,大家下来要去联系一下代码,多去测试一些情况,看看和自己预想的是不是一样的。话不多了,这篇就讲到这里,下篇我们开始讲解线程池,希望大家到时侯过来支持。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【VB测绘程序设计】案例13——几种常用的角度转换子程序Function功能的使用(附源代码)

【VB测绘程序设计】案例13——几种常用的角度转换子程序Function的使用(附源代码) 文章目录 前言一、界面展示二、程序说明三、程序代码1.角度转换子程序jdzh()四、数据演示总结前言 使用VB编写测绘程序,最基础的对于角度在导线测量计算中频繁需要角度的计算,从度分秒转…

TCP 异常断开连接【重点】

参考链接 https://xiaolincoding.com/network/3_tcp/tcp_down_and_crash.html https://xiaolincoding.com/network/3_tcp/tcp_unplug_the_network_cable.html#%E6%8B%94%E6%8E%89%E7%BD%91%E7%BA%BF%E5%90%8E-%E6%9C%89%E6%95%B0%E6%8D%AE%E4%BC%A0%E8%BE%93 关键词&#xff1a…

STM32CubeMX教程31 USB_DEVICE - HID外设_模拟键盘或鼠标

目录 1、准备材料 2、实验目标 3、模拟鼠标实验流程 3.0、前提知识 3.1、CubeMX相关配置 3.1.0、工程基本配置 3.1.1、时钟树配置 3.1.2、外设参数配置 3.1.3、外设中断配置 3.2、生成代码 3.2.0、配置Project Manager页面 3.2.1、设初始化调用流程 3.2.2、外设中…

Dialog 对应的 Context 的探究

前言 创建Dialog的时候知道在Dialog的构造方法中需要一个上下文环境&#xff0c;而对这个“上下文”没有具体的概念结果导致程序报错&#xff0c; 于是发现Dialog需要的上下文环境只能是activity。 所以接下来这篇文章将会从源码的角度来彻底的理顺这个问题。 一、Dialog创…

以太网的 MAC 层

目录 1. MAC 层的硬件地址 48 位的 MAC 地址 2. MAC 帧的格式 以太网 V2 的 MAC 帧格式 无效的 MAC 帧 IEEE 802.3 MAC 与以太网 V2 MAC 帧格式的区别 1. MAC 层的硬件地址 硬件地址又称为物理地址&#xff0c;或 MAC 地址。 IEEE 802 标准为局域网规定了一种 48 位…

企业虚拟机服务器中了lockbit3.0勒索病毒怎么办,lockbit3.0勒索病毒解密处理流程

对于企业来说&#xff0c;企业的数据是企业的核心命脉&#xff0c;关乎着企业的生产与运营的所有工作。随着网络技术的不断发展&#xff0c;网络安全威胁也在不断增加。近期&#xff0c;云天数据恢复中心接到了很多企业的求助&#xff0c;企业的虚拟机服务器遭到了lockbit3.0勒…

统计学-R语言-8.3

文章目录 前言例题例题一例题二例题三例题四例题五例题六例题七 总结 前言 本篇介绍的是有关方差知识的题目介绍。 例题 例题一 &#xff08;数据&#xff1a;exercise7_3.RData&#xff09;为研究上市公司对其股价波动的关注程度&#xff0c;一家研究机构对在主板、中小板和…

2022年至2023年广东省职业院校技能大赛高职组“信息安全管理与评估”赛项样题

2022 年至 2023 年广东省职业院校技能大赛高职组“信息安全管理与评估”赛项样题 一、 第一阶段竞赛项目试题 本文件为信息安全管理与评估项目竞赛第一阶段试题&#xff0c;第一阶段内容包 括&#xff1a;网络平台搭建、网络安全设备配置与防护。 本阶段比赛时间为 180 分钟…

代码随想录算法刷题训练营day17

代码随想录算法刷题训练营day17&#xff1a;LeetCode(110)平衡二叉树 LeetCode(110)平衡二叉树 题目 代码 /*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(…

计算机网络——TCP协议

&#x1f4a1;TCP的可靠不在于它是否可以把数据100%传输过去&#xff0c;而是 1.发送方发去数据后&#xff0c;可以知道接收方是否收到数据&#xff1b;2.如果接收方没收到&#xff0c;可以有补救手段&#xff1b; 图1.TCP组成图 TCP的可靠性是付出代价的&#xff0c;即传输效率…

Linux CPU 负载说明

一、背景 工作中我们经常遇到CPU 负载高&#xff0c;CPU负载高意味着什么&#xff1f; CPU的负载是怎么计算的&#xff1f; top指令中的各个指标代表什么含义&#xff1f; 二、CPU 负载计算方法 在系统出现负载问题&#xff0c;通常会使用uptime和top确认负载&#xff0c;这两…

项目管理平台

技术架构&#xff1a; MySQL、Servlet、JSP 功能模块&#xff1a; 从管理员角度看: 用户登入系统后&#xff0c;可以修改管理员的密码。同时具有以下功能&#xff1a; 1、管理员可以管理具体项目信息。 2、管理员可以管理项目经费信息。 3、管理员可以管理项目资源信息。 4、…

【自然语言处理】【深度学习】文本向量化、one-hot、word embedding编码

因为文本不能够直接被模型计算&#xff0c;所以需要将其转化为向量 把文本转化为向量有两种方式&#xff1a; 转化为one-hot编码转化为word embedding 一、one-hot 编码 在one-hot编码中&#xff0c;每一个token使用一个长度为N的向量表示&#xff0c;N表示词典的数量。 即&…

VsCode CMake调试QT QString等变量不显示具体值,调试中查看qt源码 (可视化调试配置Natvis)

遇到的问题 当我们在VsCode使用CMake来调试QT程序时&#xff0c;可能会出现变量是十六进制的地址&#xff0c;而看不到具体的值。例如&#xff1a; 如何解决 这时候需要手动设置一下natvis &#xff08;资源以上传&#xff0c;可以直接下载&#xff09; 在.vscode文件下找到…

C语言——操作符详解2

目录 0.过渡0.1 不创建临时变量&#xff0c;交换两数0.2 求整数转成二进制后1的总数 1.单目表达式2. 逗号表达式3. 下标访问[ ]、函数调用( )3.1 下标访问[ ]3.2 函数调用( ) 4. 结构体成员访问操作符4.1 结构体4.1.1 结构体的申明4.1.2 结构体变量的定义和初始化 4.2 结构体成…

C++学习| QT快速入门

QT简单入门 QT Creater创建QT项目选择项目类型——不同项目类型的区别输入项目名字和路径选择合适的构建系统——不同构建系统的却别选择合适的类——QT基本类之间的关系Translation File选择构建套件——MinGW和MSVC的区别 简单案例&#xff1a;加法器设计界面——构建加法器界…

配置华为交换机生成树VBST案例

知识改变命运&#xff0c;技术就是要分享&#xff0c;有问题随时联系&#xff0c;免费答疑&#xff0c;欢迎联系 厦门微思网络​​​​​​https://www.xmws.cn 华为认证\华为HCIA-Datacom\华为HCIP-Datacom\华为HCIE-Datacom 思科认证CCNA\CCNP\CCIE 红帽认证Linux\RHCE\RHC…

HTML-框架标签、实体、全局属性和元信息

HTML 1.框架标签 <iframe name"b站" src"https://www.bilibili.com" width"500" height"300" frameborder"0"></iframe>iframe 标签的实际应用&#xff1a; 在网页中嵌入广告。与超链接或表单的 target 配合&a…

Spring Cloud 之Config详解

大家好&#xff0c;我是升仔 在微服务架构中&#xff0c;统一的配置管理是维护大规模分布式系统的关键。Spring Cloud Config为微服务提供集中化的外部配置支持&#xff0c;它可以与各种源代码管理系统集成&#xff0c;如Git、SVN等。本文将详细介绍如何搭建配置服务器、管理客…

【论文笔记】GPT,GPT-2,GPT-3

参考&#xff1a;GPT&#xff0c;GPT-2&#xff0c;GPT-3【论文精读】 GPT Transformer的解码器&#xff0c;仅已知"过去"&#xff0c;推导"未来" 论文地址&#xff1a;Improving Language Understanding by Generative Pre-Training 半监督学习&#xff1…