【Hello Linux】信号量

news2025/1/20 5:55:46

作者:@小萌新
专栏:@Linux
作者简介:大二学生 希望能和大家一起进步!
本篇博客简介:简单介绍linux中信号量的概念

信号量

  • 信号量的概念
  • 信号量的使用
  • 信号量函数
    • 二元信号量模拟互斥功能
  • 基于环形队列的生产者消费者模型
    • 空间资源和数据资源
    • 生产者和消费者使用资源
    • 两个规则
    • 代码实现
    • 信号量如何保护环形队列

信号量的概念

信号量的本质就是一个计数器 它描述着临界资源中资源数目的大小

  • 临界资源:多线程执行流共享的资源叫做临界资源

拿我们现实生活中的电影院举例 一个电影院中的座位是固定的 我们这里以100个为例

假设在一场电影开场前 我们就在手机app买了一张票 那么是不是只有我们到了电影院才有一个位置属于我呢?

显然不是 我们买完票的时候就获得了一个座位从电影开场到结束的使用权 此时电影院剩余的位置就变成了99个

而信号量的作用就是计数 如果电影院剩余位置是大于0的 就接受预定 如果不是就不接受预定

我们可以将电影院的所有座位看成是一份临界资源 而单个的座位则是这个临界资源的一小份 将一个个的人看作是线程的话 那么实际上我们就是在对这份临界资源实现并发操作 而信号量则是保证临界资源安全 不会被多预定造成异常的

信号量的使用

每个执行流想要使用临界资源之前都要申请信号量 每个执行流使用完临界资源之后都要释放信号量
在这里插入图片描述

信号量的PV操作:

  • P操作:我们将申请信号量称为P操作,申请信号量的本质就是申请获得临界资源中某块资源的使用权限,当申请成功时临界资源中资源的数目应该减一,因此P操作的本质就是让计数器减一
  • V操作:我们将释放信号量称为V操作,释放信号量的本质就是归还临界资源中某块资源的使用权限,当释放成功时临界资源中资源的数目就应该加一,因此V操作的本质就是让计数器加一。

因为信号量要被多个执行流访问并申请 所以说它实际上也是一个临界资源 我们也需要对它进行加锁操作 所以说申请和释放信号量的过程必须加锁

并且当信号量为0时 申请信号量的执行流将被挂起到信号量的等待队列中 直到有信号量被释放才被唤醒 所以说虽然信号量的本质是计数器 但是信号量中却不止有计数器 还有等待队列的等

信号量函数

初始化信号量

初始化信号量的函数叫做sem_init,该函数的函数原型如下:

int sem_init(sem_t *sem, int pshared, unsigned int value);

参数说明:

  • sem:需要初始化的信号量。
  • pshared:传入0值表示线程间共享,传入非零值表示进程间共享。
  • value:信号量的初始值(计数器的初始值)。

返回值说明:

  • 初始化信号量成功返回0,失败返回-1。

销毁信号量

销毁信号量的函数叫做sem_destroy,该函数的函数原型如下:

int sem_destroy(sem_t *sem);

参数说明:

  • sem:需要销毁的信号量。

返回值说明:

  • 销毁信号量成功返回0,失败返回-1。

等待信号量(申请信号量)

等待信号量的函数叫做sem_wait,该函数的函数原型如下:

int sem_wait(sem_t *sem);

参数说明:

  • sem:需要等待的信号量。

返回值说明:

  • 等待信号量成功返回0,信号量的值减一。
  • 等待信号量失败返回-1,信号量的值保持不变。

发布信号量(释放信号量)

发布信号量的函数叫做sem_post,该函数的函数原型如下:

int sem_post(sem_t *sem);

参数说明:

  • sem:需要发布的信号量。

返回值说明:

  • 发布信号量成功返回0,信号量的值加一。
  • 发布信号量失败返回-1,信号量的值保持不变。

二元信号量模拟互斥功能

当信号量为1时 此时信号量的作用便可以等价于锁

p操作为加锁 v操作为解锁

下面是一段不加锁的抢票操作

#include <iostream>                                                                                                                                                                                                                                           
#include <unistd.h>    
#include <pthread.h>    
#include <string>    
using namespace std;    
    
int ticket = 1000;    
    
void* TicketGrabbing(void* arg)    
{    
  string name = (char *)arg;    
  while(1)    
  {    
    if (ticket > 0)    
    {    
      usleep(1000);    
      cout << name << ": get a ticket ticket left : " << --ticket << endl;    
    }    
    else    
    {    
      break;    
    }    
  }    
    
  cout << "there is no ticket" << endl;    
  pthread_exit((void *)0);    
}    
    
    
    
int main()    
{    
  pthread_t tid1 , tid2 , tid3 , tid4;    
  pthread_create(&tid1, nullptr, TicketGrabbing, (void*)"thread 1");    
  pthread_create(&tid2, nullptr, TicketGrabbing, (void*)"thread 2");    
  pthread_create(&tid3, nullptr, TicketGrabbing, (void*)"thread 3");    
  pthread_create(&tid4, nullptr, TicketGrabbing, (void*)"thread 4");    
    
  pthread_join(tid1, nullptr);    
  pthread_join(tid2, nullptr);    
  pthread_join(tid3, nullptr);    
  pthread_join(tid4, nullptr);    
  return 0;    
}    

注意 usleep必须放在if里面才能大概率出现负数票

之后编译运行我们发现这种情况

在这里插入图片描述

下面我们在抢票逻辑中加上二元信号量 让每个执行流访问临界资源的时候首先申请信号量 访问完毕释放信号量

信号量部分代码如下

class SEM    
{    
  private:    
    sem_t _sem;    
  public:    
    SEM(int num = 1)    
    {    
      sem_init(&_sem , 0 , num);    
    }    
    
    ~SEM()    
    {    
      sem_destroy(&_sem);    
    }                                                                                                                                               
    
    void P()    
    {    
      sem_wait(&_sem);    
    }    
    
    void V()    
    {    
      sem_post(&_sem);    
    }
};

之后我们只要定义一个sem对象 像使用锁一样使用它就能得到我们想要的结果

注意: 申请锁(二元信号量)的过程必须要在if之前操作 因为进入if之后申请最后别的资源释放信号量之后一定会运行下面的代码

在这里插入图片描述

我们发现这里的票数就是0了

那么这里为什么一直是2线程在抢票呢? 因为此时2线程处于活跃状态 竞争能力比较强 所以说一直是它申请到信号量

基于环形队列的生产者消费者模型

在这里插入图片描述

空间资源和数据资源

对于生产者和消费者来说 它们关注的资源是不同的

  • 生产者关注的是队列中是否还有空间 只要有空间就能进行生产
  • 消费者关注的是队列中是否还有数据 只有有数据就能进行消费

初始空间设置

我们使用两个信号量来分别设置生产者和消费者的空间 对于这个队列来说 它一开始是空的 没有数据

所以对于生产者来说 它的信号量就是空间大小 对于消费者来说 它的信号量大小就是0

生产者和消费者使用资源

对于生产者

对于生产者来说它每次申请资源需要申请空间资源(P操作) 但是生产数据完毕之后不是对于空间资源进行V操作而是对于数据资源进行V操作 这是因为申请完数据之后整个环形队列中就会多一个数据资源而不是空间资源

对于消费者

对于消费者同理 当消费者消费一个数据之后整个环形队列中会多一个空间资源 而数据资源不会变化

两个规则

生产者和消费者不能同时访问一个位置

在这里插入图片描述

  • 如果生产者和消费者同时访问一个位置 相当于它们对于同一块临界资源进行了访问 这是不被允许的
  • 当它们同时访问不同位置的时候就可以同时进行生产和消费 没有冲突

无论是生产者和消费者 都不能将对方套一个圈以上

如果是生产者将消费者套一个圈以上的话 消费者上一圈的数据都没有消费完就被覆盖了 相当于是一些数据丢失了

如果是消费者将生产者套了一个圈以上的话 生产者还没有来得及生产数据 就会产生一些异常情况

代码实现

我们STL中的vector库来实现一个环形队列

W>  1 #pragma once
    2 
    3 #include <iostream>
    4 using namespace std;
    5 #include <unistd.h>
    6 #include <pthread.h>
    7 #include <string>
    8 #include <vector>
    9 #include <semaphore.h>
   10 
   11 const int CAP = 8;
   12 
   13 
   14 template<class T>
   15 class RingQueue
   16 {
   17   private:
   18     vector<T> _q;
   19     int _cap; // 容量大小
   20     int _p_pos; // 生产者位置
   21     int _c_pos; // 消费者位置 
   22     sem_t _blank_sem; // 空间信号量
   23     sem_t _data_sem; // 数据信号量 
   24   private:
   25     void P(sem_t& s)
   26     {
   27       sem_wait(&s);                                                                                                                
   28     }
   29     void V(sem_t& s)
   30     {
   31       sem_post(&s);
   32     }
   33   public:
W> 34     RingQueue(int num = CAP)
   35       :_cap(CAP),
   36       _p_pos(0),
   37       _c_pos(0)
   38     {
   39       _q.resize(CAP);                                                                                                              
   40       sem_init(&_blank_sem , 0 , _cap);
   41       sem_init(&_data_sem , 0 , _cap);
   42     }
   43 
   44     ~RingQueue()
   45     {
   46       sem_destroy(&_blank_sem);
   47       sem_destroy(&_data_sem);
   48     }
   49 
   50     void Push(const T& data)
   51     {
   52       this->P(_blank_sem);
   53       _q[_p_pos] = data;
   54       this->V(_data_sem);
   55 
   56       // 下一次生产的位置
   57       _p_pos++;
   58       _p_pos %= _cap;
   59     }
   60 
   61     void Pop(T& data)
   62     {
   63       this->P(_data_sem);
   64       data = _q[_c_pos];
   65       this->V(_blank_sem);
   66 
   67       // 下一次消费的位置
   68       _c_pos++;
   69       _c_pos %= _cap;
   70     }
   71 
   72 };

之后我们再写一个main函数 开始运行

  1 #include "RingQueue.hpp"
  2 
  3 void* P_run(void* args)
  4 {
  5   RingQueue<int>* rq = (RingQueue<int>*)args;
  6   cout << "im producer" << endl;
  7   while(1)
  8   {
  9     int data = rand()%100+1;                                                                                                         
 10     rq->Push(data);
 11     cout << "producer :" << data << endl;
 12   }
 13 }
 14 
 15 void* C_run(void* args)
 16 {
 17   auto rq = (RingQueue<int>*)args;
 18   sleep(3);
 19   cout << "im consumer" << endl;
 20   while(1)
 21   {
 22     sleep(1);
 23     int data = 0;
 24     rq->Pop(data);
 25     cout << "consumer :" << data << endl;
 26   }
 27 }
 28 
 29 
 30 int main()
 31 {
 32   pthread_t p;
 33   pthread_t c;
 34   srand((unsigned int)time(nullptr));
 35   auto* rq = new RingQueue<int>;
 36   pthread_create(&p , nullptr , P_run , rq);
 37   pthread_create(&c , nullptr , C_run , rq);
 38 
 39   pthread_join(p , nullptr);
 40   pthread_join(c , nullptr);
 41   delete rq;
 42   return 0;
 43 }

解释下上面的main函数

我们创造了两个线程 一个作为生产者线程 一个作为消费者线程 他们共用一个环形队列

首先生产者开始生产数据三秒 消费者休眠三秒

之后消费者每休眠一秒消费一个数据

当信号量满的时候 生产者就会阻塞在那里 直到消费者消费数据 生产者才会再生产

运行代码如下

在这里插入图片描述

同时我们还可以发现 消费数据和生产数据的顺序是一样的 这就是因为环形队列的缘故

信号量如何保护环形队列

在blank_sem和data_sem两个信号量的保护后,该环形队列中不可能会出现数据不一致的问题。

只有当生产者和消费者指向环形队列的同一个位置的时候才会出现数据不一致的问题 而只有两种情况会让生产者和消费者指向同一个位置

  • 队列为空
  • 队列为满

而当队列为空的时候消费者是不能消费的

当队列为满的时候生产者是不能生产的

而除了这两种情况外其他所有情况生产者和消费者都是不能指向同一位置的 所以说信号量保护了环形队列的数据一致性问题

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

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

相关文章

CSS快速入门-选择器和优先级

文章目录CSS简介选择器CSS样式优先级CSS简介 CSS是一种用于样式化网页的语言&#xff0c;全称为“层叠样式表”&#xff08;Cascading Style Sheets&#xff09;。 它可以控制网页中元素的外观和布局&#xff0c;例如颜色、字体、大小、边距、对齐等&#xff0c;让网页变得更…

消费回暖:别总想着“报复”,而该想想怎么“修复”

01 是报复性消费吗&#xff1f;「报复性消费」一词最早是在2020年武汉疫情解封之后被大家熟知。之后的三年里&#xff0c;各路机构总是预测“等到常态化防疫结束之后&#xff0c;必将迎来真正的报复性消费”&#xff0c;事实果真如此吗&#xff1f;A面&#xff1a;涨自去年年底…

健康体检管理系统源码 PEIS源码 体检小结自动生成

健康体检管理系统源码 PEIS源码 数据对接 体检小结自动生成&#xff0c;商业源码&#xff0c;有演示&#xff0c;文档齐全。自主知识产权。 文末获取联系&#xff01; 一套专业的体检管理系统源码&#xff0c;该系统涵盖个人体检、团队体检、关爱体检等多种体检类型&#xf…

Learning Tone Curves for Local Image Enhancement

作者 LUXI ZHAO , ABDELRAHMAN ABDELHAMED , AND MICHAEL S. BROWN 论文比较清晰易懂。 就是图像分8∗8648*8648∗864个patch&#xff0c; 卷积网络为RGB三个通道预测 3∗643*643∗64 个 look up table, 就是每个patch 3个1D lut. 然后每个patch的中心直接用1D lut&#xf…

spring boot对敏感信息进行加解密

我们使用jasypt最新版本对敏感信息进行加解密。 1.在项目pom文件中加入如下依赖&#xff1a; <dependency><groupId>com.github.ulisesbocchio</groupId><artifactId>jasypt-spring-boot-starter</artifactId><version>3.0.3</version…

多维时序 | MATLAB实现CNN-GRU-Attention多变量时间序列预测

多维时序 | MATLAB实现CNN-GRU-Attention多变量时间序列预测 目录多维时序 | MATLAB实现CNN-GRU-Attention多变量时间序列预测预测效果基本介绍模型描述程序设计参考资料预测效果 基本介绍 MATLAB实现CNN-GRU-Attention多变量时间序列预测&#xff0c;CNN-GRU-Attention结合注意…

Unity记录3.4-地图-柏林噪声生成 1D 地图及过渡地图

文章首发及后续更新&#xff1a;https://mwhls.top/4489.html&#xff0c;无图/无目录/格式错误/更多相关请至首发页查看。 新的更新内容请到mwhls.top查看。 欢迎提出任何疑问及批评&#xff0c;非常感谢&#xff01; 汇总&#xff1a;Unity 记录 摘要&#xff1a;柏林噪声生成…

超详细Redis入门教程——Redis概述

前言 本文小新为大家带来 超详细Redis入门教程——Redis概述 相关知识&#xff0c;具体内容包括Redis简介&#xff0c;Redis的用途&#xff0c;Redis的特性&#xff0c;Redis的IO模型&#xff08;包括&#xff1a;单线程模型&#xff0c;混合线程模型&#xff0c;多线程模型&am…

FPGA基于SFP光口实现1G千兆网UDP通信 1G/2.5G Ethernet PCS/PMA or SGMII替代网络PHY芯片 提供工程源码和技术支持

目录1、前言2、我这里已有的UDP方案3、详细设计方案4、vivado工程详解5、上板调试验证并演示6、福利&#xff1a;工程代码的获取1、前言 目前网上的fpga实现udp基本生态如下&#xff1a; 1&#xff1a;verilog编写的udp收发器&#xff0c;但不带ping功能&#xff0c;这样的代码…

【部署】openvino2023环境配置

1. 下载 官网下载&#xff0c;我的选项如下&#xff1a; 2.配置 2.1. 在visual studio的配置 下载vs2017或者是vs2022 新建一个c项目。选择控制台应用视图–》解决方案资源管理器 在源文件下创建main.cpp文件视图–》其他窗口–》属性管理器 右键Release 添加新项目属性表…

RocketMQ单机环境搭建测试

1.资源下载 官网&#xff1a;下载 | RocketMQ 这里选择使用编译后可以直接用的 下载后解压&#xff1a;略 2.更改配置 主要是更改 conf/broker.conf 的配置&#xff0c;记得添加上下面这几行&#xff0c;否则消息发送失败 autoCreateTopicEnabletrue # 支持自动创建topic…

GitHub重量上线,开源分布式架构原理设计笔记分享

在分布式系统中&#xff0c;一次业务处理可能需要多个应用来实现&#xff0c;比如用户发送一次下单请求&#xff0c;就涉及到订单系统创建订单&#xff0c;库存系统减库存&#xff0c;而对于一次下单&#xff0c;订单创建与减库存应该是要同时成功或者同时失效&#xff0c;但在…

大气颗粒物PMF源解析

详情点击链接&#xff1a;大气颗粒物PMF源解析 一&#xff0c;PMF源解析技术及其输入文件准备 1、大气污染源解析方法有哪些&#xff1f; 2、这些方法各自应用的条件以及它们的优缺点&#xff1f; 3、大气颗粒物的基础知识及各组分的主要来源 大气颗粒物的来源&#xff1a…

逆序对问题的两种求解思路(归并排序和树状数组)

前言&#xff1a;我们在求解逆序对问题时题目往往会给我们加大数据量&#xff0c;防止我们以暴力的方式通过该题&#xff0c;所以在遇到有关求解逆序对问题的时候&#xff0c;我们有必要知道一些具体的优化方法&#xff0c;对于逆序对我们&#xff0c;我们一般的会有两种标准求…

Python实现GWO智能灰狼优化算法优化XGBoost分类模型(XGBClassifier算法)项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 灰狼优化算法(GWO)&#xff0c;由澳大利亚格里菲斯大学学者 Mirjalili 等人于2014年提出来的一种群智能…

天猫数据分析软件(天猫销售数据如何查询)

俗话说得好&#xff1a;“知己知彼&#xff0c;百战不殆”。当前&#xff0c;在天猫店铺运营中&#xff0c;大家也非常注重数据分析&#xff0c;由此能够了解到竞争对手及整个行业的销售数据及销售趋势。 一、具体来讲&#xff0c;做好天猫数据分析有哪些益处呢&#xff1f; 1、…

pytorch 数据类型

文章目录一、tensor如何表示字符串数据类型类型判断Dimension 0Dimension 1Dimension 2Dimension 3Dimension 4mixed二、创建Tensorimport from numpyimport from listuninitialized 未初始化set default typerand/rand_like, randintfulllinspaceindex切片三、维度变换总结一、…

假期怕剧荒?五一假期追剧人正确打开方式

马上就是五一假期了 趁着假期必须狠狠刷剧才是我们追剧人正确的打开方式&#xff01; 追剧人&#xff0c;追剧魂 追剧人就是快乐多&#xff01; 俗话说&#xff0c;吃饭不能没有饭碗&#xff0c;那咱们追剧也不能没有好用的追剧平台啊&#xff01; 之前因为要一次性追好几部…

React styled-components (一) —— 基本使用

https://github.com/styled-components/styled-components styled-components 基本使用介绍优点缺点安装引入使用基本用法样式嵌套介绍 styled-components 是一个针对 React 的 css in js 类库。 和所有同类型的类库一样&#xff0c;styled-components 通过 js 赋能解决了原生…

教你如何搭建物业-办公管理系统,demo可分享

1、简介 1.1、案例简介 本文将介绍&#xff0c;如何搭建物业-办公管理。 1.2、应用场景 该应用包含停车、收费、投诉、通知、访客等管理功能。 2、设置方法 2.1、表单搭建 1&#xff09;新建表单【业主信息】&#xff0c;字段设置如下&#xff1a; 名称类型名称类型类型…