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

news2024/9/24 13:25:53

目录

写在前面的话

什么是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信号量,即基于环形队列的生产消费者模型的一个实现。

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

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

相关文章

阿里云服务器镜像大全_Linux和Windows操作系统清单

阿里云服务器操作系统大全&#xff0c;阿里云提供的镜像均为正版授权&#xff0c;正版镜像可以在云服务器ECS上运行的应用程序提供安全、稳定的运行环境系统&#xff0c;阿里云服务器以公共镜像为例分享阿里云服务器操作系统大全&#xff0c;包括Alibaba Cloud Linux镜像、Linu…

洁净室气流流型分类、检测及相关法规要求概览

洁净室气流形式可以分为单向流或非单向流两种。如果综合利用两种气流&#xff0c;通常叫做混合气流。单向流可以是垂直的或水平的。两种单向流都以最终过滤的送风和回风入口&#xff0c;它们几乎是相对设置&#xff0c;这样才能使气流的流动形式保持尽可能的呈直线状。确认气流…

java全局捕捉异常并返回统一返回值

1. 首先定义全局异常处理类 import org.springframework.validation.BindException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.…

vcsa6.7 vsan超融合,虚拟机突然识别不到加密狗

问题现象&#xff1a; vcsa6.7 vsan超融合&#xff0c;虚拟机突然识别不到加密狗&#xff0c;加密狗直通给虚拟机&#xff0c;打开设备管理器&#xff0c;有未知的usb设备&#xff0c;重启虚拟机&#xff0c;卸载tools&#xff0c;重新安装tools&#xff0c;都不行 问题解决…

面向对象编程(OOP):Python中的抽象与封装

文章目录 &#x1f340;引言&#x1f340; 类与对象&#x1f340;封装&#x1f340;继承&#x1f340;多态&#x1f340;面向对象编程的优势&#x1f340;使用面向对象编程的场景&#x1f340;实例化与构造函数&#x1f340; 成员属性和类属性&#x1f340;魔术方法&#x1f34…

opencv基础:几个常用窗口方法

开始说了一些opencv中的一些常用方法。 namedWindow方法 在OpenCV中&#xff0c;namedWindow函数用于创建一个窗口&#xff0c;并给它指定一个名字。这个函数的基本语法如下&#xff1a; import cv2cv2.namedWindow(窗口名称, 标识 )窗口名称&#xff1a;其实窗口名称&…

SqlServer Management工具格式化代码、美化SQL

1、下载simple but powerful SQL formatter 插件下载地址&#xff1a;地址 2、安装 点击next—> finish 3、重新打开SqlServer Management即可看到 SQL Beautifier 点击Format ALL SQL 或者选中sql点击Format Selected SQL即可

Linux的基本权限(文件,目录)

文章目录 前言一、Linux权限的概念二、Linux权限管理 1.文件访问者分类2.文件类型和访问类型3.文件访问权限的相关设置方法三、目录的权限四、权限的总结 前言 Linux下一切皆文件&#xff0c;指令的本质就是可执行文件&#xff0c;直接安装到了系统的某种路径下 一、Linux权限的…

交换实验一

题目 交换机上接口配置 SW1 interface GigabitEthernet0/0/1 port hybrid tagged vlan 2 port hybrid untagged vlan 3 to 6 interface Ethernet0/0/2 port hybrid pvid vlan 3 port hybrid untagged vlan 2 to 6 interface Ethernet0/0/3 port link-type access port d…

【NAS群晖drive异地访问】使用cpolar远程访问内网Synology Drive「内网穿透」

文章目录 前言1.群晖Synology Drive套件的安装1.1 安装Synology Drive套件1.2 设置Synology Drive套件1.3 局域网内电脑测试和使用 2.使用cpolar远程访问内网Synology Drive2.1 Cpolar云端设置2.2 Cpolar本地设置2.3 测试和使用 3. 结语 前言 群晖作为专业的数据存储中心&…

第 3 章 稀疏数组和队列(2)

3.2 队列 3.2.1队列的一个使用场景 银行排队的案例: 3.2.2队列介绍 队列是一个有序列表&#xff0c;可以用数组或是链表来实现。遵循先入先出的原则。即:先存入队列的数据&#xff0c;要先取出。后存入的要后取出示意图:(使用数组模拟队列示意图) 3.2.3数组模拟队列思路…

[RDMA] 高性能异步的消息传递和RPC :Accelio

1. Introduce Accelio是一个高性能异步的可靠消息传递和RPC库&#xff0c;能优化硬件加速。 RDMA和TCP / IP传输被实现&#xff0c;并且其他的传输也能被实现&#xff0c;如共享存储器可以利用这个高效和方便的API的优点。Accelio 是 Mellanox 公司的RDMA中间件&#xff0c;用…

苹果支付的实现

由于app经常需要用到支付功能&#xff0c;然而ios用户&#xff0c;是无法用支付宝、微信进行支付&#xff0c;这时候只能用到苹果支付。苹果支付是苹果公司推出的一种在线支付方式&#xff0c;用户可以通过苹果支付购买应用、内购道具等等。 原理 苹果支付的实现原理是通过在…

VHDL记录

文章目录 使用function名称作为“常量”numeric_std包集中使用乘法的注意项variable的使用对于entity设置属性的方法在entity声明中嵌入function的定义VHDL仿真读写文件File declaration/File handingFile readingFile writing小例子 使用函数 模块中打印出调试信息 使用functi…

(搜索) 剑指 Offer 12. 矩阵中的路径 ——【Leetcode每日一题】

❓剑指 Offer 12. 矩阵中的路径 难度&#xff1a;中等 给定一个 m * n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 单词必须按照字母顺序&#xff0c;通过相邻的单元格内的字母构…

.netcore grpc客户端工厂及依赖注入使用

一、客户端工厂概述 gRPC 与 HttpClientFactory 的集成提供了一种创建 gRPC 客户端的集中方式。可以通过依赖包Grpc.Net.ClientFactory中的AddGrpcClient进行gRPC客户端依赖注入AddGrpcClient函数提供了许多配置项用于处理一些其他事项&#xff1b;例如AOP、重试策略等 二、案…

CS5523规格书|MIPI转EDP方案设计|替代LT8911芯片电路原理|ASL集睿致远CS替代龙讯

ASL芯片&#xff08;集睿致远&#xff09; CS5523是一款MIPI DSI输入&#xff0c;DP/e DP输出转换芯片&#xff0c;可pin to pin替代LT8911龙讯芯片。 MIPI DSI 最多支持 4 个通道&#xff0c;每个通道的最大运行速度为 1.5Gps。对于DP 1.2输出&#xff0c;它支持1.62Gbps和2.…

一文揭露AI聊天机器人到底是怎么实现自助应答的

现在很多的企业都会使用客服系统&#xff0c;主要是想通过它们来解决企业的一些问题和需求。所有就衍生了——AI聊天机器人这个新工具&#xff0c;它是把AI人工智能运用到客户服务当中&#xff0c;让AI来帮助我们完成一些解答客户问题的操作。下面我们就来说一下AI聊天机器人是…

创建和使用分区

创建和使用分区 创建一个名为 /home/curtis/ansible/partition.yml 的 playbook&#xff1a; 该palybook包含一个paly&#xff0c;该paly在balancers主机组的主机上运行&#xff1a; 在设备vdb上创建单个主分区&#xff0c;编号为1&#xff0c;大小为1500MiB 使用ext4文件系统…

数据建模和设计for CDGP第五章——如何绘制鸭掌图

CDGP中第五章必考一个数据模型设计题&#xff0c;分值10分。 主要考点 1、围绕一个场景如&#xff08;外卖送餐、图书馆管理系统等&#xff09;进行关系型逻辑数据模型设计2、要求满足范式化&#xff08;通常为3NF&#xff09;3、突出重点的实体并描述实体间的关系 加gzh“大数…