【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.mzph.cn/news/651710.shtml

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

相关文章

JVM系列——基础知识

Java运行区域 程序计数器&#xff08;Program Counter Register&#xff09; 程序计数器是一块较小的内存空间&#xff0c;它可以看作是当前线程所执行的字节码的行号指示器。在Java虚拟机的概念模型里[1]&#xff0c;字节码解释器工作时就是通过改变这个计数器的值来选取下一…

PCIE 4.0 Equalizaiton(LTSSM 均衡流程)

1. 均衡 在Tx端有FFE&#xff08;Feed Forward Equalizer&#xff0c;前馈均衡器&#xff09;&#xff1b;在Rx端有&#xff1a;CTLE&#xff08;Continuous Time Linear Equalizer&#xff0c;连续时间线性均衡器&#xff09;和DFE&#xff08;Decision Feedback Equalizer&a…

HarmonyOS 鸿蒙应用开发 (七、HTTP网络组件 axios 介绍及封装使用)

在HarmonyOS应用开发中&#xff0c;通过HTTP访问网络&#xff0c;可以使用官方提供的ohos.net.http模块。但是官方提供的直接使用不太好使用&#xff0c;需要封装下才好。推荐使用前端开发中流行的axios网络客户端库&#xff0c;如果是前端开发者&#xff0c;用 axios也会更加顺…

【GitHub项目推荐--推荐一个开源的任务管理工具(仿X书/X钉)】【转载】

推荐一个开源的任务管理工具&#xff0c;该工具会提供各类文档协作功能、在线思维导图、在线流程图、项目管理、任务分发、即时 IM&#xff0c;文件管理等等。该开源项目使用到 Vue、Element-UI、ECharts 等技术栈。 开源地址&#xff1a;www.github.com/kuaifan/dootask 预览地…

Ribbon 体系架构解析

前面已经介绍了服务治理相关组件&#xff0c;接下来趁热打铁&#xff0c;快速通关Ribbon&#xff01;前面我们了解了负载均衡的含义&#xff0c;以及客户端和服务端负载均衡模型&#xff0c;接下来我们就来看下SpringCloud 下的客户端负载均衡组件Ribbon 的特点以及工作模型。 …

【Linux】从C语言文件操作 到Linux文件IO | 文件系统调用

文章目录 前言一、C语言文件I/O复习文件操作&#xff1a;打开和关闭文件操作&#xff1a;顺序读写文件操作&#xff1a;随机读写stdin、stdout、stderr 二、承上启下三、Linux系统的文件I/O系统调用接口介绍open()close()read()write()lseek() Linux文件相关重点 复习C文件IO相…

【计算机网络】中小型校园网构建与配置

拓扑图配置文件传送门 Packet Tracer-中小型校园网配置布局文件文件 相关文章 【计算机网络】IP协议及动态路由算法 【计算机网络】Socket通信编程与传输协议分析 【计算机网络】网络应用通信基本原理 原理 1. Network 广域网&#xff0c;WAN Wide Area Network&#xff…

花式沉默Defender

编者注&#xff1a;本文仅供学习研究&#xff0c;严禁从事非法活动&#xff0c;任何后果由使用者本人负责。 前言 总结了一下现在还能用的关闭Defender的方法&#xff0c;部分是原创&#xff0c;一部分借鉴的大佬。觉得字多的同学可以直接跳过思路查看步骤进行实操。 修改注册…

再学http

HTTP状态码 1xx 信息性状态码 websocket upgrade 2xx 成功状态码 200 服务器已成功处理了请求204(没有响应体)206(范围请求 暂停继续下载) 3xx 重定向状态码 301(永久) &#xff1a;请求的页面已永久跳转到新的url302(临时) &#xff1a;允许各种各样的重定向&#xff0c;一般…

自动驾驶和智能座舱软件介绍(二)

作者 / 阿宝 编辑 / 阿宝 出品 / 阿宝1990 自动驾驶软件介绍 自动驾驶底层操作系统及软件架构 底层可以包括多种芯片&#xff0c;以太网通信中间件保证网络通信和不同OS任务分配的确定性 Automotive uC&#xff0c;单片机&#xff0c;如英飞凌AURIX&#xff0c;运行AUTOSARB…

Github 2024-01-28 开源项目日报Top10

根据Github Trendings的统计&#xff0c;今日(2024-01-28统计)共有10个项目上榜。根据开发语言中项目的数量&#xff0c;汇总情况如下&#xff1a; 开发语言项目数量Python项目3TypeScript项目2Rust项目1HTML项目1JavaScript项目1Cuda项目1C#项目1非开发语言项目1 Nuxt&#…

XSS_Labs靶场通关笔记

每一关的方法不唯一&#xff1b;可以结合源码进行分析后构造payload&#xff1b; 通关技巧&#xff08;四步&#xff09;&#xff1a; 1.输入内容看源码变化&#xff1b; 2.找到内容插入点&#xff1b; 3.测试是否有过滤&#xff1b; 4.构造payload绕过 第一关 构造paylo…

Redis数据类型及底层实现

文章目录 1.3.1 5种基本数据类型1.3.1.1 总结篇1.3.1.2 底层源码引入篇1.3.1.2.1 redis是字典数据库KV键值对到底是什么1.3.1.2.2 数据类型视角1.3.1.2.3 数据模型解析&#xff08;重点&#xff09;1.3.1.2.4 redisObjec1.3.1.2.5 SDS 1.3.1.3 String1.3.1.3.1 底层分析1.3.1.3…

uniCloud 免费版和商用版

概述 uniCloud为每个开发者提供一个免费的服务空间&#xff0c;更低门槛按量付费是serverless的特色&#xff0c;如果没有消耗硬件资源&#xff0c;就完全不用付款serverless比传统的云主机更便宜传统云主机一旦被攻击&#xff0c;高防价格非常昂贵。而uniCloud无需支付高防费…

k8s的图形化工具rancher

1、rancher&#xff1a;是一个开源的企业级多集群的k8s管理平台 2、rancher和k8s的区别 &#xff08;1&#xff09;都是为了容器的调度和编排系统 &#xff08;2&#xff09;但rancher不仅能够调度&#xff0c;还能管理k8s集群&#xff0c;自带监控&#xff08;普罗米修斯&a…

【Linux】第三十六站:信号

文章目录 一、信号的概念1.信号概念2.前台与后台进程3.信号的处理4.硬件层面5.信号与我们的代码是异步的 二、信号的产生1.产生的方式2.键盘组合键3.kill命令4.系统调用4.1 kill系统调用4.2 raise4.3 abort 5.异常软件条件5.1 异常产生信号5.2 alarm&#xff08;软件条件产生信…

【MySQL】学习如何通过DML更新数据库的数据

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 ​&#x1f4ab;个人格言:“没有罗马,那就自己创造罗马~” #mermaid-svg-QIqURn9fNFMjLD9l {font-family:"trebuchet ms",verdana,arial,sans-serif;font-siz…

【Go 快速入门】数组 | 切片 | 映射 | 函数 | 结构体 | 方法和接收者

文章目录 数组切片append 函数copy 函数删除元素 映射delete 函数 函数init 特殊的函数defer 语句panic / recover 错误处理 类型结构体内存对齐JSON 序列化与反序列化方法和接收者 项目代码地址&#xff1a;03-ArraySliceMapFuncStruct 数组 基本格式&#xff1a;var 数组变…

Go 命令行解析 flag 包之快速上手

本篇文章是 Go 标准库 flag 包的快速上手篇。 概述 开发一个命令行工具&#xff0c;视复杂程度&#xff0c;一般要选择一个合适的命令行解析库&#xff0c;简单的需求用 Go 标准库 flag 就够了&#xff0c;flag 的使用非常简单。 当然&#xff0c;除了标准库 flag 外&#x…

Linux 网络流量相关工具

本文聚焦于网络流量的查看、端口占用查看。至于网络设备的管理和配置&#xff0c;因为太过复杂且不同发行版有较大差异&#xff0c;这里就不赘述&#xff0c;后面看情况再写。 需要注意的是&#xff0c;这里列出的每一个工具都有丰富的功能&#xff0c;流量/端口信息查看只是其…