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

news2024/9/21 18:50:33

在这里插入图片描述

文章目录

  • POSIX信号量
  • 信号量接口
    • 初始化信号量
    • 销毁信号量
    • 等待信号量
    • 发布信号量
  • 基于环形队列的生产者消费者模型
    • 单生产单消费
    • 多生产多消费

POSIX信号量

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

信号量本身是一个判断条件,是资源的预定机制,预定在外部,可以不判断资源是否满足,就可以知道内部资源的情况。

信号量接口

初始化信号量


#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);

Link with -pthread.

pshared:0表示线程间共享,非零表示进程间共享
value:信号量初始值

销毁信号量

#include <semaphore.h>

int sem_destroy(sem_t *sem);

Link with -pthread.

等待信号量

等待信号量,会将信号量的值减1

 int sem_wait(sem_t *sem);

等待成功继续往后执行,资源不足,阻塞在信号量这里

发布信号量

int sem_post(sem_t *sem);

发布信号量,表示资源使用完毕,可以归还资源了。将信号量值加1。

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

环形队列在物理结构上是一个线性结构,逻辑结构我们可以认为是一个环形结构。

环形队列有一头一为,头部进行取数据,尾部进行放数据,最开始指向同一个位置。
在这里插入图片描述

现在进行只插入数据操作,end一直进行++操作,此时end又和head相遇,也就是说,当队列为空或者为满的时候,headend是相等的。

这样就出现了歧义,head==end无法判断队列的状态,因此引入了计数器或牺牲掉一个空位置(head==end+1表示队列满了)。

上面已经了解了信号量,因此队列空和满不再是本节需要关注的问题,需要关注的是多线程如何在环形队列中进行生产和消费。

在这里插入图片描述

当队列为空的时候,理论上只能让生产者先生产;当队列为满的时候,只能让消费者先消费,这就保证在访问的时候有一定的顺序性和互斥特点。
环形队列不为空也不为满时,生产和消费的下标,一定指的不是同一个位置(head!=end),此时允许生产和消费同时进行。

结论:

  1. 不能让生产者把消费者套一个圈
  2. 不能让消费者超过生产者

通过信号量来完成上述要求,实现同步和互斥。

消费者最关心的是数据资源,生产者最关心的是空间资源。

定义两个信号量:

  1. sem_t data_sem = 0数据信号量
  2. sem_t space_sem = N空间信号量

作为生产者需要申请空间,执行P操作:P(space_sem) ,生产数据后,空间被占了,执行V操作:V(data_sem)
作为消费者需要申请资源,执行P操作:P(sem_data),一旦将数据拿走,空间就多出来了,再执行一个V操作:V(spce_sem)
因此生产者和消费者是申请自己的资源,释放对方的资源。

单生产单消费

先将环形队列生产满,然后消费一个,生产一个,体现同步特性:

//RingQueue.hpp
#pragma once
#include<iostream>
#include<string>
#include<vector>
#include<semaphore.h>

template<typename T>
class RingQueue
{
private:
    void P(sem_t &s)
    {
        sem_wait(&s);
    }

    void V(sem_t &s)
    {
        sem_post(&s);
    }

public:
    RingQueue(int max_cap)
    :_ringqueue(max_cap)
    ,_max_cap(max_cap)
    ,_c_step(0)
    ,_p_step(0)
    {
        sem_init(&_data_sem,0,0);
        sem_init(&_space_sem,0,_max_cap);
    }

    //生产者
    void Push(const T &in)
    {
        P(_space_sem);
        _ringqueue[_p_step]=in;
        _p_step++;
        _p_step%=_max_cap;
        V(_data_sem);
    }
    //消费者
    void Pop(T *out)
    {
        P(_data_sem);
        *out=_ringqueue[_c_step];
        _c_step++;
        _c_step%=_max_cap;
        V(_space_sem);
    }

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

private:
    std::vector<T> _ringqueue;
    int _max_cap;

    int _c_step;
    int _p_step;

    sem_t _data_sem;  //消费者信号量
    sem_t _space_sem;  //生产者消费量
};
//Main.cc
#include"RingQueue.hpp"
#include<iostream>
#include<pthread.h>
#include<ctime>
#include<unistd.h>

void *Consumer(void *argc)
{
    RingQueue<int> *rq=static_cast<RingQueue<int> *>(argc);

    while(true)
    {
        sleep(1);
        int data=0;
        //1.消费
        rq->Pop(&data);

        std::cout<<"Consumer -> "<<data<<std::endl;
    }
}

void *Productor(void *argc)
{
    RingQueue<int> *rq=static_cast<RingQueue<int> *>(argc);
    while(true)
    {
        //1.构造数据
        int data=rand()%10+1;

        //2.生产
        rq->Push(data);
        std::cout<<"Productor -> "<<data<<std::endl;
    }
}

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

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


    //单生产单消费
    pthread_t c,p;
    pthread_create(&c,nullptr,Consumer,rq);
    pthread_create(&p,nullptr,Productor,rq);

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

    return 0;
}

运行结果:
在这里插入图片描述

生产一个消费一个,体现互斥特点:

//Main.cc
#include"RingQueue.hpp"
#include<iostream>
#include<pthread.h>
#include<ctime>
#include<unistd.h>

void *Consumer(void *argc)
{
    RingQueue<int> *rq=static_cast<RingQueue<int> *>(argc);

    while(true)
    {
        int data=0;
        //1.消费
        rq->Pop(&data);

        std::cout<<"Consumer -> "<<data<<std::endl;
    }
}

void *Productor(void *argc)
{
    RingQueue<int> *rq=static_cast<RingQueue<int> *>(argc);
    while(true)
    {
        sleep(1);

        //1.构造数据
        int data=rand()%10+1;

        //2.生产
        rq->Push(data);
        std::cout<<"Productor -> "<<data<<std::endl;
    }
}

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

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


    //单生产单消费
    pthread_t c,p;
    pthread_create(&c,nullptr,Consumer,rq);
    pthread_create(&p,nullptr,Productor,rq);

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

    return 0;
}

在这里插入图片描述

上述测试代码是传递一个int类型的数据到阻塞队列中,也可以传递其他类型,在传递struct或者class类型时,可以封装成一个个的任务传递到环形中。

传递一个任务:

//Task.hpp
#pragma once
#include<iostream>
#include<string>
#include<functional>

class Task
{

public:
    Task()
    {}
    Task(int x,int y):_x(x),_y(y)
    {}

    void Excute()
    {
        _result=_x+_y;
    }

    void operator()()
    {
        Excute();
    }

    std::string debug()
    {
        std::string msg=std::to_string(_x)+"+"+std::to_string(_y)+"=?";
        return msg;
    }
    std::string result()
    {
        std::string msg=std::to_string(_x)+"+"+std::to_string(_y)+"="+std::to_string(_result);
        return msg;
    }

private:
    int _x;
    int _y;
    int _result;
};
//Main.cc
#include"RingQueue.hpp"
#include"Task.hpp"
#include<iostream>
#include<pthread.h>
#include<ctime>
#include<unistd.h>
//传递任务
void *Consumer(void *argc)
{
    RingQueue<Task> *rq=static_cast<RingQueue<Task> *>(argc);

    while(true)
    {
        Task t;
        //1.消费
        rq->Pop(&t);
        t();
        std::cout<<"Consumer -> "<<t.result()<<std::endl;
    }
}

void *Productor(void *argc)
{
    RingQueue<Task> *rq=static_cast<RingQueue<Task> *>(argc);
    while(true)
    {
        sleep(1);

        //1.构造数据
        int x=rand()%10+1;
        usleep(x*1000);
        int y=rand()%10+1;
        Task t(x,y);

        //2.生产
        rq->Push(t);
        std::cout<<"Productor -> "<<t.debug()<<std::endl;
    }
}

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

    RingQueue<Task> *rq=new RingQueue<Task>(5);


    //单生产单消费
    pthread_t c,p;
    pthread_create(&c,nullptr,Consumer,rq);
    pthread_create(&p,nullptr,Productor,rq);

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

    return 0;
}

运行结果:
在这里插入图片描述

多生产多消费

生产者和消费者的下标位置只有一个,在环形队列中,执行多生产多消费操作,一瞬间下标称为自己的临界资源,所以必须要加锁。多生产多消费是为了在处理放数据取数据有更好的并发度。

在这里插入图片描述

先申请锁合适还是先申请信号量合适?
如果先加锁,申请信号量的线程就是一个生产者,一旦解锁,线程又得重新申请信号量,效率地下,申请锁和申请信号量注定是串行的。如果是先申请信号量,先预定着,然后再去竞争,谁的优先级高谁就先申请到锁。这就类似于我们日常生活中现在手机上面购票,等电影快开始准备检票即可。**因此先申请信号量在加锁合适。**申请信号量本身是原子的,不会出错,先把可用的资源给线程瓜分,然后等待即可。
在这里插入图片描述


在这里插入图片描述

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

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

相关文章

网络安全之渗透测试实战-DC-3-靶机入侵

一、下载靶机DC-3&#xff0c;解压后导入Vmware Workstation https://pan.baidu.com/s/17BcSH6RqC7wuyB7PRNqOow?pwdkc12启动DC-3靶机&#xff0c;由于不知道密码&#xff0c;无需登录 二、靶机的网卡采用的是NAT模式自动获取IP地址&#xff0c;此时我们需要先获取其MAC地址…

黑神话悟空火到RD集体请假?文心快码助你速通游戏!

近期&#xff0c;一款名为《黑神话&#xff1a;悟空》的游戏席卷了朋友圈&#xff0c;以其独特的西游背景、引人入胜的剧情和极致的游戏体验&#xff0c;让众多玩家沉迷其中&#xff0c;欲罢不能。然而&#xff0c;这场“悟空风暴”也给我们的RD&#xff08;研究开发人员&#…

分贝通海外卡全新升级,引领企业全球支出管理新潮流

随着中国企业不断拓展海外市场,境外资金管理成为企业国际化进程中的一大挑战。面对复杂的国际金融环境和多变的汇率风险,企业迫切需要一个高效、安全、便捷的资金管理工具。 分贝通,全新升级海外卡,企业境外资金管理和安全保驾护航。 境外资金管理的痛点 1.汇率波动风险:国际…

Shell条件测试、if语句、case分支语句

目录 一、条件测试 1. 条件测试操作 2.test 命令 3.文件测试 4.字符串比较 5.逻辑测试 二、if语句 1.单分支结构 2. 双分支结构 3.多分支结构 三、case语句 1.case结构 一、条件测试 1. 条件测试操作 Shell环境根据命令执行后的返回状态值&#xff08;$?&#xff09;来…

C++学习笔记——求整数的和与均值

一、题目描述 二、代码 #include <iostream> #include<iomanip>using namespace std;int main() {int n;cin >> n;double a[n];for(int i0;i<n;i){cin >> a[i];}double num 0;for(int i0;i<n;i){num num a[i];}double result num / n;cout &…

dotpeek反编译dll导出代码

安装dotpeek github 官网 加载dll 导出 问题 去掉特性即可&#xff0c;安装mono.cecil using Mono.Cecil; using System;namespace DelSuppressIldasmAttribute {internal class Program{static void Main(string[] args){var input "xxxx.dll";var output &qu…

mybatis 八股文

目录 重点 mybatis如何防止sql注入 #和$的区别 mybatis一级缓存、二级缓存 为什么说 Mybatis 是半自动 ORM 映射工具&#xff1f;它与全自动的区别 基础 什么是MyBatis 谈谈你为什么用MyBatis&#xff0c;有什么优点 MyBatis有哪些缺点 如何获取自动生成的主键 属性名和…

linux 进程开机自启

linux 进程开机自启 在Linux系统中&#xff0c;要让一个进程在开机时自动启动&#xff0c;可以使用systemd服务。 以下是创建自启动服务的步骤和示例代码&#xff1a; 创建一个新的systemd服务单元文件。 文件通常位于/etc/systemd/system/目录下&#xff0c;以.service作为…

18年精诚合作!苏州金龙携Mowasalat成就卡塔尔中国客车第一品牌

8月25日&#xff0c; 卡塔尔国家运输公司Mowasalat&#xff08;以下简称“M公司”&#xff09;成立20周年暨与苏州金龙海格客车合作18周年庆典在卡塔尔首都多哈隆重召开。卡塔尔交通部部长贾西姆本赛义夫苏莱提&#xff0c;中国驻卡塔尔使馆经商处参赞杨松、M公司董事长萨阿德艾…

无人机之航拍的优势

无人机航拍在多个方面展现出了显著的优势&#xff0c;这些优势使其在航拍领域具有强大的竞争力和广泛的应用前景。以下是无人机航拍的主要优势&#xff1a; 一、 独特的视角与视觉震撼 独特视角&#xff1a;无人机航拍提供了与传统拍摄截然不同的视角&#xff0c;尤其是垂直正…

小米手机误删照片如何恢复?

在日常使用手机的过程中&#xff0c;我们难免会遇到误删照片的情况&#xff0c;尤其是在整理相册时一不小心就可能将珍贵的回忆永久删除。但别担心&#xff0c;小米手机提供了多种方法来帮助你恢复误删的照片。本文将为你详细介绍三种有效的恢复策略&#xff0c;帮助你找回那些…

【CSS in Depth 2 精译_021】3.4 负的外边距 + 3.5 外边距折叠

当前内容所在位置&#xff08;可进入专栏查看其他译好的章节内容&#xff09; 第一章 层叠、优先级与继承&#xff08;已完结&#xff09; 1.1 层叠1.2 继承1.3 特殊值1.4 简写属性1.5 CSS 渐进式增强技术1.6 本章小结 第二章 相对单位&#xff08;已完结&#xff09; 2.1 相对…

【思源笔记】思源笔记配置S3同步

本文首发于 ❄️慕雪的寒舍 文章目录 1. 写在前面2. 什么是思源笔记的S3/WEBDAV同步&#xff1f;2.1. 说明2.2. 思源的同步配置和工作空间2.3. 什么是S3协议&#xff1f; 3. 配置思源S3同步3.1. 初始化数据仓库密钥3.2. 思源S3同步界面3.3. 配置七牛云KODO3.4. 如何将同步配置导…

排队免单小程序

本文来自&#xff1a;排队免单小程序 - 源码1688 应用介绍 排队免单小程序是基于移动互联网技术开发的平台系统&#xff0c;通过小程序的形式为消费者和商家搭建了一个互动桥梁。以下是对排队免单小程序的详细介绍&#xff1a; 一、基本概念 排队免单小程序是一种创新的营销工…

Qt与SVG

Qt之SVG&#xff1a;Qt简单使用SVG的介绍_qt svg-CSDN博客 <?xml version"1.0" standalone"no"?> <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xml…

15 种高级 RAG 技术 ——从预检索到生成

15 种高级 RAG 技术 ——从预检索到生成 检索增强生成&#xff08;RAG&#xff09;是一个丰富、快速发展的领域&#xff0c;它为增强由大型语言模型&#xff08;LLM&#xff09;驱动的生成式人工智能系统创造了新的机会。在本指南中&#xff0c;WillowTree的数据与人工智能研究…

工业一体机,为工业自动化提供完美解决方案

近年来&#xff0c;智能制造成为全球制造业转型升级的必然趋势&#xff0c;而工业自动化作为智能制造的关键环节&#xff0c;其重要性日益凸显。在这一趋势下&#xff0c;工业一体机凭借其高度集成、稳定可靠、灵活高效等优势&#xff0c;成为了工业自动化领域中不可或缺的“利…

Mysql的查询指令

整理了一些Mysql的查询语句&#xff0c;希望对大家有帮助&#xff0c;祝大家心想事成万事如意&#xff01; 基本查询 select 字段 from 表名 where 条件&#xff1b; 排序查询 select 字段 from 表名 order by 排序字段 [asc升序|desc降序] limit 前几行/中间几行&#xff1…

Nature methods | FlowSig--揭示细胞间流动网络新方法!有具体代码可实操!

–https://doi.org/10.1038/s41592-024-02380-w Inferring pattern-driving intercellular flows from single-cell and spatial transcriptomics FlowSig是刚刚&#xff08;8月26日&#xff09;发表在nature methods上的新文章&#xff0c;该方法系统性地推断出由细胞间通信…

Ubuntu系统使用Docker部署中文版trilium并实现远程编辑笔记

文章目录 前言1. 安装docker与docker-compose2. 启动容器运行镜像3. 本地访问测试4.安装内网穿透5. 创建公网地址6. 创建固定公网地址 前言 今天和大家分享一款在G站获得了26K的强大的开源在线协作笔记软件&#xff0c;Trilium Notes的中文版如何在Linux环境使用docker本地部署…