【POSIX信号量】

news2025/1/18 17:10:23

文章目录

  • 1 :peach:POSIX信号量 :peach:
  • 2 :peach:信号量的接口介绍 :peach:
    • 2.1 :apple:初始化信号量:apple:
    • 2.2 :apple:销毁信号量:apple:
    • 2.3 :apple:等待信号量:apple:
    • 2.4 :apple:发布信号量:apple:
  • 3 :peach:基于环形队列的生产者消费者模型 :peach:
    • 3.1 :apple:环形队列:apple:
    • 3.2 :apple:基于环形队列的单生产者单消费者模型代码实现:apple:
    • 3.3 :apple:基于环形队列的多生产者多消费者模型代码实现:apple:


1 🍑POSIX信号量 🍑

我们可以先来回忆回忆下什么是信号量?
信号量本质是一种计数器,是对资源的一种预订机制,也就是说只要你信号量申请成功了,那么就说明该资源在某个时刻一定是属于你的,不会有别的线程来占有。这个听起来是不是有写熟悉呀?感觉有点儿像互斥锁,其实互斥锁本身就是一种二元信号量,而信号量是可以划分成多元的,也就是说资源可以被分成了多份,我们可以申请这些被分成的小资源。
说到信号量,就不得不提到PV操作,P操作是申请资源,V操作是释放资源。(这个不知道的可以自行下去百度下,很快就能够理解)

POSIX信号量和SystemV信号量作用相同,都是用于同步操作,达到无冲突的访问共享资源目的。 但POSIX可以用于线程间同步。


2 🍑信号量的接口介绍 🍑

2.1 🍎初始化信号量🍎

#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
参数:
pshared:0表示线程间共享,非零表示进程间共享
value:信号量初始值

2.2 🍎销毁信号量🍎

int sem_destroy(sem_t *sem);

2.3 🍎等待信号量🍎

功能:等待信号量,会将信号量的值减1
int sem_wait(sem_t *sem); //P()

2.4 🍎发布信号量🍎

功能:发布信号量,表示资源使用完毕,可以归还资源了。将信号量值加1int sem_post(sem_t *sem);//V()

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

3.1 🍎环形队列🍎

在上篇文章讲解生产者消费者模型的时候,我们使用的是阻塞队列来帮助我们实现的。现在我们要换一种方式来实现生产者消费者模型,也就是使用循环队列来帮助我们实现。

什么是循环队列呢?其实并不一定就是一个队列,较为简单是直接用数组模拟实现出来的,比如下面就是一个较为简单的循环队列模型,我们可以将一个大的资源,划分成为了一个一个的小资源,通过对小资源的同步和互斥要求来达到实现生产者消费者模型的目的:
在这里插入图片描述
但是现在问题来了,我们如何指定规则让环形队列构建生产者消费者模型呢?
我们不妨构建一个生产者和一个消费者,刚开始让生产者和消费者指向同一个位置,然后让生产者先跑,消费者在后面追,注意生产者先跑了后,消费者和生产者可以同时跑,但是必须要满足下面的规则:

  • 不能让生产者套圈消费者
  • 不能让消费者超过生产者

当满足上面规则后我们就能够完成一个生产者消费者的模型了。
特别注意:

  • 当环形队列为空时,应该让生产者先运行(同步)
  • 当环形队列为满时,应该让消费者先运行(同步)
  • 其余情况时生产者消费者可以并发运行

3.2 🍎基于环形队列的单生产者单消费者模型代码实现🍎

在讲解代码实现前我们应该先想想环形队列的成员应该有哪些?
首先来说,我们可以用一个vector模拟队列,记录一个容器大小的整形变量,除此之外因为生产者只关心空间资源,所以我们还得加一个空间资源信号量_space_sem,同理,消费者只关心数据资源,所以还要加一个数据资源信号量_data_sem,但是我们push和pop数据方便我们还得加上当前生产者与消费者的位置_p_idx_c _idx有了这些理解后我们就可以上手撸代码了:

#pragma once
#include<iostream>
#include<vector>
#include<pthread.h>
#include <semaphore.h>
const int N=5;
using namespace std;

template<class T>
class ringQueue
{

public:
    ringQueue(size_t num=N)
        :_cap(num)
        ,_q(num)
        {
            
            sem_init(&_space_sem,0,num);
            sem_init(&_data_sem,0,0);
            _p_idx=_c_idx=0;
        }

    void P(sem_t* psem)
    {
        sem_wait(psem);
    }

    void V(sem_t* psem)
    {
        sem_post(psem);
    }

    void push(const T& data)//生产者push数据,消耗了空间资源,增加了数据资源
    {
        P(&_space_sem);
        _q[_p_idx++]=data;
        _p_idx%=_cap;
        V(&_data_sem);
    }
    void pop(T& data)//消费者pop数据,消耗了数据资源,增加了空间资源
    {
        P(&_data_sem);
        data=_q[_c_idx++];
        _c_idx%=_cap;
        V(&_space_sem);
    }

    ~ringQueue()
    {
        sem_destroy(&_space_sem);
        sem_destroy(&_data_sem);
    }

private:
    vector<T> _q;
    size_t _cap;
    sem_t _space_sem;//生产者关心空间资源
    sem_t _data_sem;//消费者关心数据资源
    int _p_idx;//生产位置
    int _c_idx;//消费位置
};

从上面的代码中我们看到:与之前讲解阻塞队列的情况有些不太一样,因为我们并没有在里面加锁,为什么呢?
原因其实很简单,因为我们在实现线程库给我们提供的wait操作时如果条件不满足会阻塞在那里,直至条件满足;并且我们生产者与消费者在非空和非满的情况下是可以并发运行的;我们实现的是单生产者单消费者模型不会存在多个生产者或者多个消费者共同抢占同一份资源的情况。
我们可以下一个测试程序来看看:

#include<iostream>
#include<time.h>
#include<unistd.h>
#include"RingQueue.hpp"
using namespace std;

void* pRun(void* args)
{
    ringQueue<int>* prq=static_cast<ringQueue<int>* >(args);
    while(true)
    {
        sleep(1);
        int data=rand()%10+1;
        prq->push(data);
        cout<<"productor make "<<data<<endl;
    }
    return nullptr;
}

void* cRun(void* args)
{
    ringQueue<int>* prq=static_cast<ringQueue<int>* >(args);
    while(true)
    {
        int data;
        prq->pop(data);
        cout<<"consumer deal "<<data<<endl;
    }
    return nullptr;
}

int main()
{
    srand((size_t)time(nullptr));
    ringQueue<int>* prq=new ringQueue<int>(5);
    pthread_t p,c;
    pthread_create(&p,nullptr,pRun,prq);
    pthread_create(&c,nullptr,cRun,prq);

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

当我们运行时:
在这里插入图片描述
至于打印的结果有时候看着是乱的是因为我们在准备任务资源以及处理任务是可以并发进行的。

3.3 🍎基于环形队列的多生产者多消费者模型代码实现🍎

大家想想看,上面的代码能够完成多生产者多消费者的场景吗?
仔细思考下,我们发现其实是不能够的。因为在多生产者多消费者在多线程的情况下是可以并发的抢占资源的,而我们更新资源的代码并没有加锁,所以是会造成线程安全的问题。
那我们应该加几把锁呢?
答案肯定是两把,注意不能够只加一把锁。因为我们是不能够让多个生产者线程共同进入临界区,也不能够让多个消费者线程共同进入临界区,但是生产者和消费者在某些情况下是可以并发进入临界区的。
所以我们就不妨来改改代码:

#pragma once
#include<iostream>
#include<vector>
#include<pthread.h>
#include <semaphore.h>
const int N=5;
using namespace std;

template<class T>
class ringQueue
{

public:
    ringQueue(size_t num=N)
        :_cap(num)
        ,_q(num)
        {
            
            sem_init(&_space_sem,0,num);
            sem_init(&_data_sem,0,0);
            _p_idx=_c_idx=0;
            pthread_mutex_init(&_p_mutex,nullptr);
            pthread_mutex_init(&_c_mutex,nullptr);
        }

    void P(sem_t* psem)
    {
        sem_wait(psem);
    }

    void V(sem_t* psem)
    {
        sem_post(psem);
    }

    void push(const T& data)//生产者push数据,消耗了空间资源,增加了数据资源
    {
        pthread_mutex_lock(&_p_mutex);
        P(&_space_sem);
        _q[_p_idx++]=data;
        _p_idx%=_cap;
        V(&_data_sem);
        pthread_mutex_unlock(&_p_mutex);
    }
    void pop(T& data)//消费者pop数据,消耗了数据资源,增加了空间资源
    {
        pthread_mutex_lock(&_c_mutex);
        P(&_data_sem);
        data=_q[_c_idx++];
        _c_idx%=_cap;
        V(&_space_sem);
        pthread_mutex_unlock(&_c_mutex);
    }

    ~ringQueue()
    {
        sem_destroy(&_space_sem);
        sem_destroy(&_data_sem);
        pthread_mutex_destroy(&_p_mutex);
        pthread_mutex_destroy(&_c_mutex);
    }

private:
    vector<T> _q;
    size_t _cap;
    sem_t _space_sem;//生产者关心空间资源
    sem_t _data_sem;//消费者关心数据资源
    int _p_idx;//生产位置
    int _c_idx;//消费位置
    pthread_mutex_t _p_mutex;//防止多个生产者共同进入临界区
    pthread_mutex_t _c_mutex;//防止多个消费者共同进入临界区

};

但是上面的代码还有一个小地方还可以优化,我们来看看这里:
在这里插入图片描述我们是把加锁放在了P操作之前,这样做有必要吗?我们想想,其实这种是没有必要的,因为P操作的实现本身就是原子操作,所以不可能申请到同一份资源,如何我们将其放在锁的外面,其实还可以多线程并发的申请不同的资源,提高效率;同理V操作释放资源因该放在临界区之外。所以我们极力推荐下面这种写法:

在这里插入图片描述

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

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

相关文章

插入排序讲解

插入排序&#xff08;Insertion-Sort&#xff09;一般也被称为直接插入排序。对于少量元素的排序&#xff0c;它是一个有效的算法。插入排序是一种最简单的排序方法&#xff0c;它的基本思想是将一个记录插入到已经排好序的有序表中&#xff0c;从而一个新的、记录数增1的有序表…

JDK8:Optional详解与源码分析,如何优雅的处理空指针

文章目录 一、Optional概述1、烦人的NullPointerException2、Optional简介 二、Optional使用1、创建Optional对象2、isPresent()与ifPresent()应用&源码解析3、get()应用&源码解析4、orElseThrow()应用&源码解析5、map()应用&源码解析6、flatMap()应用&源码…

string【2】模拟实现string类(超详解哦)

string模拟实现 引言&#xff08;实现概述&#xff09;string类方法实现默认成员函数构造函数拷贝构造赋值运算符重载析构函数 迭代器beginend 容量size、capacity、emptyreserveresize 访问元素operator[] 修改insert插入字符插入字符串 appendpush_backoperatoreraseclearswa…

BGP----边界网关路协议

目录 一&#xff0c;BGP相关的特点 二、BGP特点&#xff1a; 三、BGP数据包 四、BGP的工作过程 五、名词 六、BGP的路由黑洞问题 七、BGP的防环机制 –水平分割 八、BGP的基本配置&#xff1a; 1.直连的EBGP邻居关系建立 2.IBGP邻居关系建立 3.EBGP邻居间存在多条…

【NetCore】06-配置框架

文章目录 1.配置框架1.1 核心包1.2 配置框架核心类型1.3 配置框架扩展点 2.命令行配置提供程序2.1 支持的命令格式2.2 命令替换模式 3. 环境变量配置提供程序3.1 使用场景3.2 特性 4.文件配置提供程序4.1 文件配置提供程序4.2 特性 5.配置变更监听-配置热更新能力的核心5.1 场景…

客服如何通过微信接收消息通知-唯一客服文档中心

当我们在自己网站上嵌入对接了客服代码&#xff0c;我们想要通过微信接收访客的消息提醒通知&#xff0c;可以通过扫描客服后台的微信二维码&#xff0c;即时收消息通知提醒。 我们网站地址&#xff1a;gofly.v1kf.com 客服后台 后台主页面板&#xff0c;就展示了一个微信二维码…

PHP http请求封装使用(POST.GET,PUT,DELETE)

封装的 sendRequest() 函数是一个通用的发送请求函数&#xff0c;可以发送 POST、GET、PUT、DELETE 请求。下面对该函数的代码进行具体讲解&#xff1a; <?php function sendRequest($method, $url, $data null, $contentType multipart/form-data, $headers array(),…

无人驾驶实战-第一课(自动驾驶概述)

在七月算法上报了《无人驾驶实战》课程&#xff0c;老师讲的真好。好记性不如烂笔头&#xff0c;记录一下学习内容。 ————————————————————————————————————————— 无人驾驶汽车的定义&#xff1a; 无人驾驶汽车是可载人的移动智能机器…

【ASP.NET MVC】动态与静态网站(3)

一、区别 静态网页&#xff08;站&#xff09; 用户通过浏览器提交访问需求&#xff0c;需求可以是默认首页或者指定的网站中的某个页面&#xff0c;WEB服务器查找对应的网页&#xff0c;通过HTTP协议发送到客户端&#xff0c;完成访问。 特点&#xff1a;每次访问、不同角色…

视频监控综合管理平台EasyCVR修改参数提示database or disk is full是什么原因?

EasyDarwin开源流媒体视频EasyCVR安防监控平台可提供视频监控直播、云端录像、云存储、录像检索与回看、智能告警、平台级联、云台控制、语音对讲、智能分析等能力。视频监控综合管理平台EasyCVR具备视频汇聚融合能力&#xff0c;平台基于云边端一体化架构&#xff0c;具有强大…

【MCU学习】GD32F427VG开发

&#xff08;一&#xff09;学习文档和例程 兆易创新GD32 MCU参考资料下载 1.GD232F4xx的Keil芯片支持包 2.标准固件库和示例程序 3.GD32F4xx_固件库使用指南_Rev1.2 4.用户手册&#xff1a;GD32F4xx_User_Manual_Rev2.8_CN 5.数据手册&#xff1a;GD32F427xx_Datasheet_Rev…

通信笔记——最小移频键控MSK

由2FSK→MSK存在以下几点&#xff1a; 1、如何实现已调信号的码元正交&#xff1b;2、如何实现相位连续&#xff0c;不突变。 1、以2FSK一般表示法出发&#xff0c;推导得出两种频率的约束关系 正交条件&#xff1a; 积化和差公式有&#xff1a; 当载波频率比较高&#xff…

类欧几里得算法学习笔记

偶然发现了学长发给我的一个学长的学长也是我的学长的一个数论 p p t ppt ppt&#xff0c;先不着急复习莫反杜教筛&#xff0c;按这个顺序来吧 0.随便说说 前一阵子确实学习状态不是很好&#xff0c;我感觉我个人学习状态也是忽好忽坏的&#xff0c;不过只要在学习状态好的时…

大麦链接源码 大麦一键生成订单截图

8.4最新版源码 更新了大麦链接模版 更新了大麦订单截图一键生成 下载源码&#xff1a;https://pan.baidu.com/s/16lN3gvRIZm7pqhvVMYYecQ?pwd6zw3

TM4C123的ROM函数和非ROM函数区别

前言 在开发TM4C123的时候&#xff0c;我看到ROM函数和非ROM函数。例如ROM_FPUEnable()和FPUEnable()函数&#xff0c;这两个就只是前缀不一样。有什么区别和不同&#xff1f; 相同点 ROM函数和非ROM函数的作用起始是一样的&#xff0c;比如上面的例子ROM_FPUEnable()和FPUEnab…

如何有效地扩展数据库服务器以满足日益增长的工作量

在当今以数据为驱动的世界中&#xff0c;企业面临着一个挑战&#xff0c;即在保证应用程序的最佳性能的同时&#xff0c;管理迅速增长的数据量。扩展数据库服务器在满足这些需求方面起着至关重要的作用。本篇博客将探讨各种策略&#xff0c;以有效地扩展数据库服务器&#xff0…

[C++]

C 一.C基础入门1.HelloWorld2.注释3.变量4.常量5.关键字6.命名规则 二.数据类型1.整形2.sizeof关键字3.浮点型4.字符型5.转义字符6.字符串型7.布尔类型8.数据的输入 三.运算符1.算数运算符2.赋值运算符3.比较运算符4.逻辑运算符 一.C基础入门 1.HelloWorld 首先到官网下载并安…

P2824 [HEOI2016/TJOI2016] 排序(线段树)(内附封面)

[HEOI2016/TJOI2016] 排序 题目描述 在 2016 2016 2016 年&#xff0c;佳媛姐姐喜欢上了数字序列。因而她经常研究关于序列的一些奇奇怪怪的问题&#xff0c;现在她在研究一个难题&#xff0c;需要你来帮助她。 这个难题是这样子的&#xff1a;给出一个 1 1 1 到 n n n 的…

关于单测技术选型,聊聊我的思考

对于单测来说&#xff0c;目前常用的单测框架有&#xff1a; JUnitMockitoSpockPowerMockJMockitTestableMock 其中 JUnit 不支持 Mock&#xff0c;因此基本不会只用 JUnit&#xff0c;而是结合其他有 Mock 功能的框架一起使用。从知名度及使用率来说&#xff0c;Mockito 和 …

【基于HBase和ElasticSearch构建大数据实时检索项目】

基于HBase和ElasticSearch构建大数据实时检索项目 一、项目说明二、环境搭建三、编写程序四、测试流程 一、项目说明 利用HBase存储海量数据&#xff0c;解决海量数据存储和实时更新查询的问题&#xff1b;利用ElasticSearch作为HBase索引&#xff0c;加快大数据集中实时查询数…