【Linux】POSIX信号量和基于环形队列的生产消费者模型

目录

写在前面的话

什么是POSIX信号量

POSIX信号量的使用

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


 

写在前面的话

        本文章主要先介绍POSIX信号量,以及一些接口的使用,然后再编码设计一个基于环形队列的生产消费者模型来使用这些接口。

        讲解POSIX信号量时,首先需要对信号量有一定的了解,大家可以去看我的这一篇文章:systemV信号量,文章的前面我详细的说明了什么是信号量以及对它的理解。


什么是POSIX信号量

        POSIX信号量(POSIX semaphore)是一种线程同步机制,用于管理共享资源的并发访问。POSIX信号量是基于POSIX标准定义的一组函数和数据类型,旨在提供跨平台的线程同步能力。

        POSIX信号量允许线程在访问共享资源之前获取一个信号量,通过增加或减少信号量值来控制对资源的访问。当信号量值大于零时,线程可以获取资源并继续执行。当信号量值为零时,线程将被阻塞,直到其他线程释放资源并增加信号量的值。这样可以有效地实现对共享资源的互斥访问和线程之间的同步。


POSIX信号量的使用

  • sem_init()用于初始化一个信号量对象。

函数原型如下:

int sem_init(sem_t *sem, int pshared, unsigned int value);
  • pshared:0表示线程间共享,非零表示进程间共享
  • value:信号量初始值
  • sem_destroy()用于销毁一个信号量对象。

函数原型如下:

int sem_destroy(sem_t *sem)
  • sem_wait()尝试获取一个信号量,如果信号量值大于零,则将其减少并继续执行;否则,线程将被阻塞。

函数原型如下:

int sem_wait(sem_t *sem); //P()
  • sem_post():释放一个信号量,将其值增加,唤醒可能正在等待该信号量的线程。

函数原型如下:

int sem_post(sem_t *sem);//V()
  • sem_trywait():与 sem_wait() 类似,但是尝试获取信号量时不阻塞线程,而是立即返回。
  • sem_timedwait():与 sem_wait() 类似,但是可以设置超时时间,如果在超时时间内仍无法获取信号量,则返回错误。

以上两个函数了解即可.


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

        这个相当于改变了交易场所,由阻塞队列变成了 环形队列.

下面是利用环形队列的几种策略:

  • 环形队列采用数组模拟,用模运算来模拟环状特性

  • 但是上面的设计有一个问题,假设head生产,tail消费;就是当head和tail重合的时候,我们不知道到底是head == tail还是tail == head, 即不知道此时环形队列为空还是为满。所以可以通过加计数器或者标记位来判断满或者空。另外也可以预留一个空的位置,作为满的状态,这个原理大家可以去网上查找环形队列的相关知识,这个置空位恰好将head和tail隔开。

  • 但是现在有了信号量这个计数器,所以也能轻松地实现 线程间利用环形队列同步的过程。 

整体代码设计如下:

        首先用类设计一个环形队列RingQueue,并封装一些接口push()和pop(),成员变量为一个vector数组(用数组模拟实现环形队列)、int num_ 用来表示环形队列的大小,c_step表示消费者的下标,p_step表示生产者的下标,然后有两个信号量space_sem_data_sem_,分别表示空间资源信号量和数据资源信号量。一开始的时候没有数据,所有空间都可使用,所以我们将空间资源信号量space_sem_设置为环形队列的长度ndata_sem_设置为0,表示没有数据。

        信号量的数据类型为sem_t,但是为了方便,我对信号量进行了一个封装,类Sem,里面包含了信号量的初始化,p()和v()操作等,然后上面两个信号量的类型就直接使用Sem.

然后在RingQueue类中,两个接口push和pop的实现逻辑如下:

  • push():这是生产者所使用的,生产者关注的是空间资源,如果有空间就生产,没有就停止。 生产后空间资源信号量space_sem_-1,但是数据资源信号量data_sem_+1,然后每次就在对应位置上写入相应的数据即可,记得模上数组的长度,因为逻辑结构是一个环形队列。
  • pop():这是消费者所使用的,消费者关注的是数据资源,有数据就消费,没数据就不能消费。消费后数据资源信号量data_sem_-1,但是空间资源信号量space_sem+1,同样地将对应位置的数据取出即可。

所以环形队列RingQueue类和Sem类的代码如下:

Ringqueue.hpp类

#pragma once
#include<iostream>
#include<pthread.h>
#include<vector>
#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_(g_default_num),num_(g_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();
    }

private:
    vector<T> ring_queue_;
    int num_;
    int c_step;//消费者下标
    int p_step;//生产者下标
    Sem space_sem_;
    Sem data_sem_;
};

Sem.hpp类

#pragma once
#include "ringQueue.hpp"
#include <semaphore.h>

class Sem
{
public:
    Sem(int val)
    {
        sem_init(&sem_,0,val);
    }
    void p()
    {
        sem_wait(&sem_);
    }
    void v()
    {
        sem_post(&sem_);
    }
    ~Sem()
    {
        sem_destroy(&sem_);
    }
private:
    sem_t sem_;
};

然后我们对代码进行测试,测试代码如下,和上一节的测试代码几乎一样:

#include "ringQueue.hpp"
#include<sys/types.h>
#include<unistd.h>
#include <time.h>
using namespace std;
void* consumer(void* args)
{
    RingQueue<int>* rq = (RingQueue<int>*)args;
    while(true)
    {
        int x;
        //1.从环形队列中拿取数据
        rq->pop(&x);
        //2.进行一定的处理
        cout << "消费: " << x << endl; 

    }
}
void* producter(void* args)
{
    RingQueue<int>* rq = (RingQueue<int>*)args;
    while ((true))
    {
        //1.构建数据或任务对象 -- 一般可以从外部来,不要忽略时间消耗问题
        int x = rand() % 100 + 1;
        cout << "生产: " << x << endl; 
        //2.推送到环形队列中
        rq->push(x);//完成生产的过程
        // sleep(1);

    }
    
}

int main()
{
    srand((unsigned int)time(nullptr) ^ getpid() ^ 12366 );
    RingQueue<int>* rq = new RingQueue<int>();
    pthread_t c,p;
    // rq->debug();
    pthread_create(&c,nullptr,consumer,(void*)rq);
    pthread_create(&p,nullptr,producter,(void*)rq);

    pthread_join(c,nullptr);
    pthread_join(p,nullptr);
    return 0;
}

代码也成功的执行了:

 


        上面是单线程,即单生产者,单消费者。

        我们也改成多线程并发执行的,这也是生产消费者模型的意义所在。

        当多线程并发执行时,如果两个线程 同时访问临界资源可能出错,所以需要在临界资源前后加上锁,使得只能有一个线程可以访问临界资源。

        但是这样和单线程有什么区别啊,都是单线程访问,多线程的意义在哪里?

        首先不要狭隘的认为,把任务或数据放在交易场所就是生产和消费了。将数据或任务拿到之后的处理,才是最耗费时间的,虽然拿的时候是加锁一个个拿的,但是处理的时候,却是一起处理的所以生产消费者模型主要意义体现在可以并发的处理任务

  • 生产的本质:私有的任务 -> 公共空间中
  • 消费的本质:公共空间中 -> 私有的

信号量本质是一把计数器 那计数器的意义是什么?

        可以不用进入临界区,就可以得知资源的情况,甚至可以减少临界区内部的判断。

申请锁 -> 判断临界资源和访问 -> 释放锁 ---> 本质我们并不清楚临界资源的情况,信号量要提前预设资源的情况,而且在pv变化中,我们在外部就可以知道临界资源的情况.

        所以我们在RingQueue类中,加入两个锁,分别为生产者和消费者:

 主函数中,创建多个线程:

 

 运行后,可以发现不同的线程生产和消费任务了。

这便是本章的全部内容了,主要讲述了POSIX信号量,即基于环形队列的生产消费者模型的一个实现。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

rabbitMQ服务自动停止(已解决

1、 在rabbitmq的sbin目录下操作 rabbitmq-plugins enable rabbitmq_management 2、 自己去rabbitmq_server-3.7.5文件夹下创建一个data&#xff0c;再执行这个命令&#xff08;用自己的目录哈 set RABBITMQ_BASED:\RabbitTools\RabbitMQ\rabbitmq_server-3.7.5\data 然后去配…

执行Lua脚本后一直查询不到Redis中的数据(附带问题详细排查过程,一波三折)

文章目录 执行Lua脚本后一直查询不到Redis中的数据&#xff08;附带详细问题排查过程&#xff0c;一波三折&#xff09;问题背景问题1&#xff1a;Lua脚本无法切库问题2&#xff1a;RedisTemlate切库报错问题3&#xff1a;序列化导致数据不一致问题4&#xff1a;Lua脚本中单引号…

windows系统丢失mfc120u.dll的解决方法

1.mfc120u.dll是什么 mfc120u.dll是Windows操作系统中的一个动态链接库&#xff08;Dynamic Link Library&#xff0c;简称DLL&#xff09;文件。它包含了一些用于运行C程序的函数和其他资源。这个特定的DLL文件是Microsoft Foundation Classes&#xff08;MFC&#xff09;库的…

wireshark界面内容含义

网络分析工具——WireShark的使用&#xff08;超详细&#xff09;_世间繁华梦一出的博客-CSDN博客 wireshark抓包数据&#xff1a;理解与分析_wireshark里面length_ 佚名的博客-CSDN博客

app 自动化测试 - 多设备并发 -appium+pytest+ 多线程

1、appiumpython 实现单设备的 app 自动化测试 启动 appium server&#xff0c;占用端口 4723电脑与一个设备连接&#xff0c;通过 adb devices 获取已连接的设备在 python 代码当中&#xff0c;编写启动参数&#xff0c;通过 pytest 编写测试用例&#xff0c;来进行自动化测试…

python优雅地爬虫!

背景 我需要获得新闻&#xff0c;然后tts&#xff0c;在每天上班的路上可以听一下。具体的方案后期我也会做一次分享。先看我喜欢的万能的老路&#xff1a;获得html内容-> python的工具库解析&#xff0c;获得元素中的内容&#xff0c;完成。 好家伙&#xff0c;我知道我爬…

Data Abstract for .NET and Delphi Crack

Data Abstract for .NET and Delphi Crack .NET和Delphi的数据摘要是一套或RAD工具&#xff0c;用于在.NET、Delphi和Mono中编写多层解决方案。NET和Delphi的数据摘要是一个套件&#xff0c;包括RemObjects.NET和Delphi版本的数据摘要。RemObjects Data Abstract允许您创建访问…

Vue使用jspdf和html2canvas组件库结合导出PDF文件

效果图&#xff1a; 1、安装依赖&#xff1a; npm install html2canvas --save npm install jspdf --save 或 yarn add html2canvas --save yarn add jspdf --save 2、封装全局调用方法&#xff1a;this.$exportPDF(#id,文件名) 新建js文件&#xff1a;/utils/html2Pdf.js&am…

Mysql性能优化:什么是索引下推?

导读 索引下推&#xff08;index condition pushdown &#xff09;简称ICP&#xff0c;在Mysql5.6的版本上推出&#xff0c;用于优化查询。 在不使用ICP的情况下&#xff0c;在使用非主键索引&#xff08;又叫普通索引或者二级索引&#xff09;进行查询时&#xff0c;存储引擎…

QtCreator中设置自定义注释格式

QtCreator--工具--选项--文本编辑器--片段--组:C--添加 在其中添加一个key为&#xff1a;header&#xff0c;value如下图的组合&#xff1a; /*! ProjName : %{CurrentProject:Name}* FileName : %{CurrentDocument:FileName}* Brief : * Details : * Aut…

(三) 搞定SOME/IP通信之CommonAPI库

本章主要介绍在SOME/IP通信过程中的另外一个IPC通信利剑&#xff0c;CommonAPI库&#xff0c;文章将从如下几个角度让读者了解什么是CommonAPI, 以及库在实际工作中的作用 文中资源&#xff1a;vsomeipcommonapi指导文档与demo源码 SOME/IP通信之CommonAPI CommonAPI库是什么C…

Java虚拟机(JVM):堆溢出

一、概念 Java堆溢出&#xff08;Java Heap Overflow&#xff09;是指在Java程序中&#xff0c;当创建对象时&#xff0c;无法分配足够的内存空间来存储对象&#xff0c;导致堆内存溢出的情况。 Java堆是Java虚拟机中用于存储对象的一块内存区域。当程序创建对象时&#xff0c…

设计模式之简单工厂模式

一、概述 定义一个用于创建对象的接口&#xff0c;让子类决定实例化哪一个类。工厂模式使一个类的实例化延迟到其子类。 简单工厂模式&#xff1a;又叫做静态工厂方法模式&#xff0c;是由一个工厂对象决定创建出哪一种产品类的实例。 二、适用性 1.当一个类不知道它所必须…

MySQL 账号权限

mysql 在安装好后&#xff0c;默认是没有远端管理账号。 一、账号管理 1. 查看账号列表 MySQL用户账号和信息存储在名为 mysql 的数据库中。一般不需要直接访问 mysql 数据库和表&#xff0c;但有时需要直接访问。例如&#xff0c;查看数据库所有用户账号列表时。 USE mysql; …

Matplotlib数据可视化(二)

目录 1.rc参数设置 1.1 lines.linestype取值 1.2 lines.marker参数的取值 1.3 绘图中文预设 1.4 示例 1.4.1 示例1 1.4.2 示例2 1.rc参数设置 利用matplotlib绘图时为了让绘制出的图形更加好看&#xff0c;需要对参数进行设置rc参数设置。可以通过以下代码查看matplotli…

揭秘!体育比赛是如何快人一步购票的

最近&#xff0c;各类体育赛事正如火如荼的进行中&#xff0c;作为资深体育迷&#xff0c;看着赛场上的英雄们正在为荣誉和胜利而拼搏&#xff0c;内心也跟着激情澎湃起来。 为了享受精彩纷呈的赛事&#xff0c;越来越多体育迷选择亲临现场&#xff0c;感受更真实的比赛氛围&a…

VR仿真实训系统编辑平台赋予老师更多自由和灵活性

为了降低院校教师在VR虚拟现实方面应用的门槛&#xff0c;VR公司深圳华锐视点融合多年的VR虚拟仿真实训系统制作经验&#xff0c;制作了VR动物课件编辑器&#xff0c;正在逐渐受到师生们的关注和应用。 简单来说&#xff0c;VR畜牧专业课件编辑器是一种可以制作虚拟现实动物教学…

【WPF】 本地化的最佳做法

【WPF】 本地化的最佳做法 资源文件英文资源文件 en-US.xaml中文资源文件 zh-CN.xaml 资源使用App.xaml主界面布局cs代码 App.config辅助类语言切换操作类资源 binding 解析类 实现效果 应用程序本地化有很多种方式&#xff0c;选择合适的才是最好的。这里只讨论一种方式&#…

HTTP响应状态码大全:从100到511,全面解析HTTP请求的各种情况

文章目录 前言一、认识响应状态码1. 什么是HTTP响应状态码2. Http响应状态码的作用3. 优化和调试HTTP请求的建议 二、1xx 信息响应1. 认识http信息响应2. 常见的信息响应状态码 三、2xx 成功响应1. 认识HTTP成功响应2. 常见的成功响应状态码 四、3xx 重定向1. 认识http重定向2.…

WS2812B————动/静态显示

一&#xff0c;系统架构 二&#xff0c;芯片介绍 1.管脚说明 2.数据传输时间 3.时序波形 4.数据传输方法 5.常用电路连接 三&#xff0c;代码展示及说明 驱动模块 在驱动模块首先选择使用状态机&#xff0c;其中包括&#xff0c;空闲状态&#xff0c;复位清空状态&#xff0c…