Linux——信号量

news2024/11/18 5:29:48

目录

POSIX信号量

信号量原理

信号量概念

信号量函数

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

生产者和消费者申请和释放资源

单生产者单消费者

多生产者多消费者

多生产者多消费者的意义

信号量的意义


POSIX信号量

信号量原理

  1. 如果仅用一个互斥锁对临界资源进行保护,相当于把这块临界资源看作一个整体,同一时间只允许一个执行流对这块临界资源进行访问
  2. 如果将这块临界资源再分成多个区域,当多个执行流需要访问临界资源时,如果这些执行流访问的是临界资源的不同区域,那就可以继续并发访问,访问同一个资源的时候再进行同步和互斥

信号量概念

        信号量本质上就是一个计数器,记录的是临界资源中资源数目的计数器,它可以更细粒度的对临界资源进行管理。

        访问临界资源的时候,先申请信号量信号量的值--,这叫做预定资源,我们叫做P操作;使用完后,信号量的值++,这叫做释放资源,我们叫做V操作

        当我们申请了一个信号量,可以保证当前执行流一定具有一个资源,要说是哪一个那就需要我们自己编写代码决定了。

信号量函数

作用:初始化信号量

参数:

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

返回值:成功返回0,失败返回-1,错误码被设置。

 

作用:销毁信号量

参数:要销毁的信号量

返回值:成功返回0,失败返回-1,错误码被设置。

 

作用:等待信号量

参数:要等待的信号量

返回值:

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

 

作用:释放信号量

参数:需要释放的信号量

返回值:

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

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

        生产者最看重的就是队列中的空间,而消费者最看重的就是队列中的数据。只要有空间就可以生产数据,只要有数据就可以消费。

        用信号量来表示上述的数据:

  • spaceSem:表示有多少空间,开始设为N(队列长度)。
  • dataSem:表示有多少数据,开始设为0。

生产者和消费者申请和释放资源

        当生产数据的时候:

P(spaceSem) 
    spaceSem--;
// 生产数据
V(dataSem)
    dataSem++;

        当消费数据的时候

P(dataSem)
    dataSem--;
// 消费数据
V(spaceSem)
    spaceSem++;

        当两个执行流同时访问的时候:

  • 如果消费者先执行,要P(申请)dataSem(数据),但是一开始的dataSem的值是0,所以就被阻塞了。
  • 如果生产者先运行,要P(申请)spaceSem(空间),一开始的spaceSem的值是N,就可以申请成功,生产数据,之后V(释放)dataSem(数据)。
  • 如果生产者把数据放满了,要P(申请)spaceSem(空间)就会失败,生产者就被阻塞。
  • 如果两个执行流都能获取想要的资源,那就可以实现并发访问。

单生产者单消费者

// Sem.hpp
#include <iostream>
#include <pthread.h>
#include <semaphore.h>

// 封装一下信号量
class Sem
{
public: 
    Sem(int value)
    {
        sem_init(&_sem, 0, value);
    }
    void P()
    {
        sem_wait(&_sem);
    }
    void V()
    {
        sem_post(&_sem);
    }
    ~Sem()
    {
        sem_destroy(&_sem);
    }
private:
    sem_t _sem; 
};
// ringQueue.hpp
#include <iostream>
#include <vector>
#include <pthread.h>
#include <unistd.h>
#include "Sem.hpp"

using namespace std;

const int g_default_num = 5;

template <class T>
class RingQueue
{
public:
    RingQueue(int default_num = g_default_num)
        :_ring_queue(default_num)
        ,_num(default_num)
        ,_c_step(0)
        ,_p_step(0)
        ,_space_sem(default_num)
        ,_data_sem(0)
    {}
    ~RingQueue()
    {}
    // 生产者
    void push(const T& in)
    {
        _space_sem.P();
        _ring_queue[_p_step++] = in;
        _p_step %= _num;
        _data_sem.V();
    }
    // 消费者
    void pop(T* out)
    {
        _data_sem.P();
        *out = _ring_queue[_c_step++];
        _c_step %= _num;
        _space_sem.V();
    }
    void debug()
    {
        cout << "size: " << _num << "queue: " << _ring_queue.size() << endl;
    }

private:
    vector<T> _ring_queue;
    size_t _num;
    int _c_step; // 消费者下标
    int _p_step; // 生产者下标
    Sem _space_sem; // 空间信号量
    Sem _data_sem; // 数据信号量
};
// testMain.cc

#include "ringQueue.hpp"
#include <ctime>

void* consumer(void* args)
{
    RingQueue<int>* rq = (RingQueue<int>*)args;
    while (true)
    {
        int x;
        // 1.从环形队列中获取任务
        rq->pop(&x);
        // 2.进行处理
        cout << "消费:" << x << endl;
        // sleep(1);
    }
}

void* productor(void* args)
{
    RingQueue<int>* rq = (RingQueue<int>*)args;
    while (true)
    {
        // 1.构建数据或任务对象,数据可能从任何地方来,那一定会有时间消耗
        int x = rand() % 10 + 1;
        // 2.推送到环形队列
        rq->push(x);
        cout << "生产:" << x << endl;
        // sleep(1);
    }
}

int main()
{
    srand((unsigned)time(nullptr) ^ getpid());

    RingQueue<int>* rq = new RingQueue<int>();

    pthread_t c, p;
    pthread_create(&c, nullptr, consumer, (void*)rq);
    pthread_create(&p, nullptr, productor, (void*)rq);

    pthread_join(c, nullptr);
    pthread_join(p, nullptr);

    return 0;
}

        与基于阻塞队列的生产者消费者模型不同的是,阻塞队列是一块临界资源,就会有互斥和同步的问题,生产者和消费者访问临界资源的时候就要加互斥锁来保护临界资源,用信号量实现没有使用互斥锁,我们把资源的数目规定好,通过管理这些资源的数量,就可以对每一块资源更细粒度的管理。
        关于环形队列的实现就不过多赘述了,就是控制下标加上模运算。生产者消费者只要访问的不是环形队列中的相同区域,他们两个基本就没有关系,所以可以实现并发访问。我们维护的只有生产者和消费者之间的互斥和同步关系

多生产者多消费者

        如果是多生产和多消费该怎么做呢?我们要知道的是相比于单生产单消费要多维护什么关系,其实就是生产和生产间、消费和消费间的这两种互斥关系

        如果只加一把锁,本来生产和消费可以有很大概率并发执行,现在又多了锁的竞争,就可能变成串行执行,一把不行,那就加两把。

        生产者之间的临界资源就是空间,消费者之间的临界资源就是数据。

        既然有了锁就可以保护临界资源了,那么我是先申请信号量还是先申请锁呢?假如先申请锁,锁申请成功了,再申请信号量,此时就可能有很多信号量还没有分配出去,前面我们也说过,这个信号量是一种预定机制,即便申请了信号量也没有使用资源,那为何不先申请信号量呢,所以一般都是先申请信号量再加锁。

 

const int g_default_num = 5;

template <class T>
class RingQueue
{
public:
    RingQueue(int default_num = g_default_num)
        :_ring_queue(default_num)
        ,_num(default_num)
        ,_c_step(0)
        ,_p_step(0)
        ,_space_sem(default_num)
        ,_data_sem(0)
    {
        pthread_mutex_init(&_clock, nullptr);
        pthread_mutex_init(&_plock, nullptr);
    }
    ~RingQueue()
    {
        pthread_mutex_destroy(&_clock);
        pthread_mutex_destroy(&_plock);
    }
    // 生产者
    void push(const T& in)
    {
        _space_sem.P();
        pthread_mutex_lock(&_plock);
        _ring_queue[_p_step++] = in;
        _p_step %= _num;
        pthread_mutex_unlock(&_plock);
        _data_sem.V();
    }
    // 消费者
    void pop(T* out)
    {
        _data_sem.P();
        pthread_mutex_lock(&_clock);
        *out = _ring_queue[_c_step++];
        _c_step %= _num;
        pthread_mutex_unlock(&_clock);
        _space_sem.V();
    }
    void debug()
    {
        cout << "size: " << _num << "queue: " << _ring_queue.size() << endl;
    }

private:
    vector<T> _ring_queue;
    size_t _num;
    int _c_step;            // 消费者下标
    int _p_step;            // 生产者下标
    Sem _space_sem;         // 空间信号量
    Sem _data_sem;          // 数据信号量
    pthread_mutex_t _clock; // 消费者之间的锁
    pthread_mutex_t _plock; // 生产者之间的锁
};

多生产者多消费者的意义

        其实生产者往容器缓冲区中放数据和消费者从容器缓冲区中拿数据,就是一个生产者在放,一个消费者在拿,那它的意义在哪呢?

        我们要思考的是,我们从哪里拿到的任务也就是生产任务前,我们拿到任务后该怎么做,如果只有一个执行流,它既要做这个也要做那个,中间还得加锁,那任务就是一个一个做的,如果使用多线程,那么多个线程就可以并发的处理这些动作。

信号量的意义

        信号量的意义是什么呢?

        看到这里是一定会带有问题的,阻塞队列时,我们要先申请锁,再检测,不成功就阻塞,唤醒后在检测,成功后再执行,但是使用信号量都没有检测,甚至可能都没有加锁。

        其实阻塞队列中我们并不清楚临界资源的情况,但信号量是一个计数器,它可以预定某种资源,在PV操作中我们也可以知道临界资源的情况。

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

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

相关文章

Vulnhub靶机:Kioptrix_Level1.2

一、介绍 运行环境&#xff1a;Virtualbox 攻击机&#xff1a;kali&#xff08;192.168.56.101&#xff09; 靶机&#xff1a;Kioptrix_Level1.2&#xff08;192.168.56.106&#xff09; 目标&#xff1a;获取靶机root权限和flag 靶机下载地址&#xff1a;https://www.vul…

【3月14日-云服务器推荐】阿里云 腾讯云 京东云有什么区别? 选购指南 最新价格对比 搭建博客 游戏服务器均可多用

3月14日更新&#xff0c;本文纯原创&#xff0c;侵权必究 《最新对比表》已更新在文章头部—腾讯云文档&#xff0c;文章具有时效性&#xff0c;请以腾讯文档为准&#xff01; 【腾讯文档实时更新】云服务器1分钟教会你如何选择教程 https://docs.qq.com/document/DV0RCS0lGeH…

郭炜老师mooc第十一章数据分析和展示(numpy,pandas, matplotlib)

多维数组库numpy numpy创建数组的常用函数 # numpy数组import numpy as np #以后numpy简写为np print(np.array([1,2,3])) #>>[1 2 3] print(np.arange(1,9,2)) #>>[1 3 5 7] 不包括9 print(np.linspace(1,10,4)) #>>[ 1. 4. 7. 10.] # linespace(x,y,n)&…

基于YOLOv8/YOLOv7/YOLOv6/YOLOv5的日常场景下的人脸检测系统(深度学习模型+PySide6界面+训练数据集+Python代码)

摘要&#xff1a;开发用于日常环境中的人脸识别系统对增强安全监测和提供定制化服务极为关键。本篇文章详细描述了运用深度学习技术开发人脸识别系统的全过程&#xff0c;并附上了完整的代码。该系统搭建在强大的YOLOv8算法之上&#xff0c;并通过与YOLOv7、YOLOv6、YOLOv5的性…

YOLOv9如何训练自己的数据集(NEU-DET为案列)

&#x1f4a1;&#x1f4a1;&#x1f4a1;本文内容&#xff1a;教会你用自己数据集训练YOLOv9模型 YOLOv9魔术师专栏 ☁️☁️☁️☁️☁️☁️☁️☁️☁️☁️☁️☁️☁️☁️☁️☁️☁️☁️ ☁️☁️☁️☁️☁️☁️☁️☁️☁️☁️☁️☁️☁️☁️ 包含注意力机制魔…

没有硬件基础可以学单片机吗?

没有硬件基础可以学单片机吗&#xff1f; 在开始前我分享下我的经历&#xff0c;我刚入行时遇到一个好公司和师父&#xff0c;给了我机会&#xff0c;一年时间从3k薪资涨到18k的&#xff0c; 我师父给了一些 电气工程师学习方法和资料&#xff0c;让我不断提升自己&#xff0c…

Docker-基本命令

目录 一、Docker与虚拟机技术 二、Docker功能 三、安装 安装&#xff1a; 1、环境准备&#xff1a; 2、安装docker 3、配置阿里云镜像加速 镜像加速源 4、Docker是怎么工作的 5、Docker为什么比虚拟机快 四、docker命令 1、镜像命令 Docker官方镜像库&#xff1a…

深入理解与应用Keepalive机制

目录 引言 一、VRRP协议 &#xff08;一&#xff09;VRRP概述 1.诞生背景 2.基本理论 &#xff08;二&#xff09;VRRP工作原理 &#xff08;三&#xff09;VRRP相关术语 二、keepalive基本理论 &#xff08;一&#xff09;基本性能 &#xff08;二&#xff09;实现原…

Hadoop伪分布式配置--没有DataNode或NameNode

一、原因分析 重复格式化NameNode 二、解决方法 1、输入格式化NameNode命令&#xff0c;找到data和name存放位置 ./bin/hdfs namenode -format 2、删除data或name&#xff08;没有哪个删哪个&#xff09; sudo rm -rf data 3、重新格式化NameNode 4、重新启动即可。

Seata 2.x 系列【7】服务端集成 Nacos 2.x

有道无术&#xff0c;术尚可求&#xff0c;有术无道&#xff0c;止于术。 本系列Seata 版本 2.0.0 本系列Spring Boot 版本 3.2.0 本系列Spring Cloud 版本 2023.0.0 源码地址&#xff1a;https://gitee.com/pearl-organization/study-seata-demo 文章目录 1. 概述2. 安装 N…

Cassandra 集群安装部署

文章目录 一、概述1.官方文档2. 克隆服务器3.安装说明4.安装准备4.1.安装 JDK 114.2.安装 Python4.3.下载文件 二、安装部署1.配置 Cassandra2.启动 Cassandra3.关闭Cassandra4.查看状态5.客户端连接服务器6.服务运行脚本 开源中间件 # Cassandrahttps://iothub.org.cn/docs/m…

校园新闻网站|基于SpringBoot+ Mysql+Java+B/S结构的校园新闻网站设计与实现(可运行源码+数据库+设计文档+部署说明)

推荐阅读100套最新项目 最新ssmjava项目文档视频演示可运行源码分享 最新jspjava项目文档视频演示可运行源码分享 最新Spring Boot项目文档视频演示可运行源码分享 目录 前台功能效果图 管理员功能登录前台功能效果图 系统功能设计 数据库E-R图设计 lunwen参考 摘要 研究…

运放的基础知识

运算放大器&#xff08;Operational Amplifier&#xff0c;简称运放&#xff09;是一种直流耦合、差模&#xff08;差动模式&#xff09;输入的高增益电压放大器&#xff0c;通常具有单端输出。它能产生一个相对于输入端电势差大数十万倍的输出电势&#xff08;对地而言&#x…

爬虫的去重

去重基本原理 爬虫中什么业务需要使用去重 防止发出重复的请求防止存储重复的数据 在爬取网页数据时&#xff0c;避免对同一URL发起重复的请求&#xff0c;这样可以减少不必要的网络流量和服务器压力&#xff0c;提高爬虫的效率&#xff0c;在将爬取到的数据存储到数据库或其…

ConnectionResetError: [WinError 10054] 远程主机强迫关闭了一个现有的连接。

发生的错误信息&#xff1a; File "C:\Users\malongqiang\.conda\envs\ObjectDetection\lib\ssl.py", line 1309, in do_handshakeself._sslobj.do_handshake() ConnectionResetError: [WinError 10054] 远程主机强迫关闭了一个现有的连接。 分析原因&#xff1a; …

网络编程:网络编程基础

一、网络发展 1.TCP/IP两个协议阶段 TCP/IP协议已分成了两个不同的协议&#xff1a; 用来检测网络传输中差错的传输控制协议TCP 专门负责对不同网络进行2互联的互联网协议IP 2.网络体系结构 OSI体系口诀&#xff1a;物链网输会示用 2.1网络体系结构概念 每一层都有自己独…

新生宿舍管理系统|基于springboot框架+ Mysql+Java+B/S架构的新生宿舍管理系统设计与实现(可运行源码+数据库+设计文档+部署说明)

推荐阅读100套最新项目 最新ssmjava项目文档视频演示可运行源码分享 最新jspjava项目文档视频演示可运行源码分享 最新Spring Boot项目文档视频演示可运行源码分享 目录 学生功能模块 管理员功能 系统功能设计 数据库E-R图设计 lunwen参考 摘要 研究目的 开发环境 项目部…

全栈的自我修养 ———— js中的复制api

通常用于可以禁止用户复制或者在复制的内容后面添加版权信息等 一、代码二、展示1、访问粘贴板的内容2、替换复制内容3、在复制内容的后面添加版权信息4、监听粘贴事件 一、代码 <body><div class"demo">不可以被复制</div><div class"de…

Linux中mysql的安装、远程访问、基础操作、文件导入

Linux中mysql的安装、远程访问、基础操作、文件导入 cheet card1. 安装1. 使用root账号安装mysql 2. 启动mysql并创建root、管理员两个账号3. 基础操作3.1 数据库的查看、创建、修改、删除3.2 mysql的数据类型3.3 数据表的基本操作3.4 数据表结构的修改3.5 表中数据的增、删、改…

【C++ 学习】程序内存分布

文章目录 1. C 内存分布的引入 1. C 内存分布的引入 ① 栈又叫堆栈&#xff1a;非静态局部变量/函数参数/返回值等等&#xff0c;栈是向下增长的。 ② 内存映射段&#xff1a;是高效的I/O映射方式&#xff0c;用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存…