【Linux】生产者消费者模型——环形队列RingQueue(信号量)

news/2024/4/27 0:37:06/文章来源:https://blog.csdn.net/weixin_60478154/article/details/130333834

文章目录

  • 铺垫
  • 信号量
    • 信号量概念
    • 信号量PV操作
    • 信号量基本接口
  • 环形队列的生产消费模型
    • 引入环形队列
    • 访问环形队列
    • 代码实现
    • 代码改造
    • 多生产者多消费者代码
  • 总结

铺垫

之前写的代码是存在不足的地方的:

image-20230423174805160

我们使用线程操作临界资源的时候要先去判断临界资源是否满足条件:并不能事前得知,只能通过先加锁判断,再检测,再操作、解锁,因为我们在操作临界资源的时候,有可能不就绪,但是我们无法提前得知,所以只能先加锁再检测,根据检测结果,决定下一步怎么走,那我们能不能通过一种办法提前得知是否满足条件呢?这样就不用加锁了,直接让线程等待或者访问:答案就是信号量

信号量

信号量概念

什么是信号量?

只要我们对资源进行整体加锁就默认了我们对这个资源整体使用,实际情况可能存在一份公共资源,但是允许同时访问不同的区域!(程序员编码保证不同的线程可以并发访问公共资源的不同区域!)

  • 信号量本质是一把计数器,衡量临界资源中资源数量多少的计数器

  • 只要拥有信号量,就在未来一定能够拥有临界资源的一部分,申请信号量的本质:对临界资源中特定小块资源的预定机制。**比如电影院买票预定座位

  • 只要申请成功,就一定有你的资源,只要申请失败,就说明条件不就绪,你只能等,就不需要判断了

线程要进行访问临界资源中的某一区域——得先申请信号量——前提是所有人必须先看到信号量——所以信号量本身必须是:公共资源。

信号量PV操作

P操作:sem–,申请操作,必须保证操作的原子性

V操作:sem++,归还资源,必须保证操作的原子性

信号量的核心操作就是PV原语

信号量基本接口

初步看一下信号量的基本使用接口:

#include <semaphore.h>//信号量初始化
int sem_init(sem_t *sem, int pshared, unsigned int value)
//sem:自己定义的信号量变量
//pshared:0表示线程间共享,非零表示进程间共享。
//value:信号量初始值(资源数目)。//信号量销毁
int sem_destroy(sem_t *sem)//信号量等待
int sem_wait(sem_t *sem):p操作,--//信号量发布
int sem_pos(sem_t *sem):V操作,++

环形队列的生产消费模型

引入环形队列

环形队列之前我们就了解过了,只要是环形队列,就存在判空判满的问题。实际上并不是真正的环形队列,而是通过数组模拟的,当数据加入到最后的位置时直接模等于数组的大小即可。通常情况下,判空判满的问题我们是通过空出一个位置,当两个指针指向同一个位置的时候是空,当只剩一个位置的时候就是满,但是我们这里不需要关注。

访问环形队列

生产者和消费者访问同一个位置的情况:空的时候,满的时候;其他情况下生产者与消费者访问的就是不同的区域了。

为了完成环形队列的生产消费,我们的核心工作就是

1.消费者不能超过生产者

2.生产者不能套消费者一个圈以上

3.生产者和消费者指向同一个位置时,如果此时满了就让消费者先走,如果此时为空就让生产者先走

大部分情况下生产者与消费者是并发执行的,但是当环形队列为空或为满的时候就会存在着同步与互斥问题。

如何去进行保证:信号量维护,信号量是衡量临界资源中资源数量的

资源是什么:

1.对于生产者,看中的是队列中的剩余空间,空间资源定义成一个信号量

2.对于消费者,看中的是队列中的数据资源,数据资源定义成一个信号量

比如我们一共有10个位置,消费者初始信号量是0,生产者初始信号量是10,如果生产者线程生产数据,申请信号量,进行P操作,信号量变为9,申请失败则阻塞;申请成功后消费者线程看到了多一个数据资源,消费者信号量进行V操作.所以我们并不需要进行判空判满:当生产者生产满了,信号量申请不到,进行阻塞,只能让消费者先走;当消费者消费完了,信号量申请不到,只能让生产者先走

代码实现

单生产单消费的环形队列生产者消费者模型,利用随机数生成数据资源,通过生产线程与消费线程进行数据的生成与数据的消费:

#pragma once
#include <iostream>
#include <vector>
#include <cassert>
#include <semaphore.h>
static const int gcap = 5;
template<class T>
class RingQueue
{
private:void P(sem_t&sem){int n  =sem_wait(&sem);assert(n==0);(void)n;}void V(sem_t&sem){int n = sem_post(&sem);assert(n==0);(void)n;}
public:RingQueue(const int&cap = gcap):_queue(cap),_cap(cap){int n = sem_init(&_spaceSem,0,_cap);assert( n == 0);n = sem_init(&_dataSem, 0, 0);assert(n==0);  _productorStep = _consumerStep = 0;}//生产者——空间void Push(const T&in){P(_spaceSem);//申请到了空间信号量,意味着我们一定能进行正常的生产_queue[_productorStep++] = in;_productorStep%=_cap;V(_dataSem);}//消费者——数据void Pop(T *out){P(_dataSem);*out = _queue[_consumerStep--];_consumerStep%=_cap;V(_spaceSem);}~RingQueue(){sem_destroy(&_spaceSem);sem_destroy(&_dataSem);}
private:std::vector<T> _queue;int _cap;sem_t _spaceSem;//生产者想生产,看中空间资源sem_t  _dataSem;//消费者想消费,看中数据资源int _productorStep;int _consumerStep;
};
#include "RingQueue.hpp"
#include <pthread.h>
#include <ctime>
#include <cstdlib>
#include <sys/types.h>
#include <unistd.h>
void*ProductorRoutine(void*rq)
{RingQueue<int>*ringqueue = static_cast<RingQueue<int>*>(rq);while(true){int data = rand()%10+1;ringqueue->Push(data);std::cout<<"生产完成,生产的数据是:"<<data<<std::endl;}
}
void*ConsumerRoutine(void*rq)
{RingQueue<int>*ringqueue = static_cast<RingQueue<int>*>(rq);while(true){int data;ringqueue->Pop(&data);std::cout<<"消费完成,消费的数据是:"<<data<<std::endl;sleep(1);}
}
int main()
{srand((unsigned int) time(nullptr)^getpid()^pthread_self()^0x7432);RingQueue<int>*rq = new RingQueue<int>();pthread_t p,c;pthread_create(&p,nullptr,ConsumerRoutine,rq);pthread_create(&c,nullptr,ProductorRoutine,rq);pthread_join(p,nullptr);pthread_join(c,nullptr);delete rq;return 0;
}

代码改造

实际上,生产线程和消费线程可不单单只能通过整型,我们还可以生产和消费任务,下面,我们只需要进行简单的改造即可完成:

Task.hpp:完成计算器的任务:计算两个数的加减乘除模

对于任务类Task:包含两个数x与y,以及计算方式op,以及计算的回调方法callback。

同时为了后面的生产线程和消费线程能够清楚看到过程,提供了两个方法一个是重载(),把计算的结果保存于字符串并放回,此方法用于消费者线程在队列中取出任务,把结果打印出来;另一个方法是toTaskString(),把计算的过程保存于字符串并返回,此方法用于生产者线程生产任务存放队列中,并且可以把过程打印出来

外部通过构造任务类对象t,传入生成的随机数x与y,以及随机生成的计算方式op,同时传入了计算的方法mymath,进行计算。

Task.hpp:

#pragma once
#include <iostream>
#include <functional>
#include <cstdio>
#include <cstring>
class Task
{using func_t = std::function<int(int,int,char)>;
public:Task(){}Task(int x,int y,char op,func_t func):_x(x),_y(y),_op(op),_callback(func){}std::string operator()(){int result = _callback(_x,_y,_op);char buffer[1024];snprintf(buffer,sizeof buffer,"%d %c %d = %d",_x,_op,_y,result);return buffer;}std::string toTaskString(){char buffer[1024];snprintf(buffer,sizeof buffer,"%d %c %d = ?",_x,_op,_y);return buffer;}
private:int _x;int _y;char _op;func_t _callback;
};
const std::string oper = "+-*/%"; 
int mymath(int x,int y,char op)
{int result = 0;switch (op){case '+':result = x + y;break;case '-':result = x - y;break;case '*':result = x * y;break;case '/':{if (y == 0){std::cerr << "div zero error!" << std::endl;result = -1;}elseresult = x / y;}break;case '%':{if (y == 0){std::cerr << "mod zero error!" << std::endl;result = -1;}elseresult = x % y;}break;default:break;}return result;
}

Main.cc

#include "RingQueue.hpp"
#include "Task.hpp"
#include <pthread.h>
#include <ctime>
#include <cstdlib>
#include <sys/types.h>
#include <unistd.h>
void*ProductorRoutine(void*rq)
{RingQueue<Task>*ringqueue = static_cast<RingQueue<Task>*>(rq);while(true){//构建任务int x = rand()%10;int y = rand()%5;char op = oper[rand()%oper.size()];Task t(x,y,op,mymath);//生产任务ringqueue->Push(t);std::cout<<"生产者派发了一个任务:"<<t.toTaskString()<<std::endl;//sleep(1);}
}
void*ConsumerRoutine(void*rq)
{RingQueue<Task>*ringqueue = static_cast<RingQueue<Task>*>(rq);while(true){//构建任务Task t;//消费任务ringqueue->Pop(&t);std::string result = t();std::cout<<"消费者消费了一个任务:"<<result<<std::endl;sleep(1);}
}
int main()
{srand((unsigned int) time(nullptr)^getpid()^pthread_self()^0x7432);RingQueue<Task>*rq = new RingQueue<Task>();pthread_t p,c;pthread_create(&p,nullptr,ConsumerRoutine,rq);pthread_create(&c,nullptr,ProductorRoutine,rq);pthread_join(p,nullptr);pthread_join(c,nullptr);delete rq;return 0;
}

多生产者多消费者代码

只要保证,最终进入临界区的是一个生产,一个消费就行,所以我们需要在环形队列提供的Push与Pop加锁,所以环形队列提供了多两个成员变量:一个是生产线程的锁,一个是消费线程的锁,也就是需要加两把锁,你拿你的,我拿我的

RingQueue.hpp

#pragma once
#include <iostream>
#include <vector>
#include <cassert>
#include <semaphore.h>
#include <pthread.h>
static const int gcap = 5;
template<class T>
class RingQueue
{
private:void P(sem_t&sem){int n  =sem_wait(&sem);assert(n==0);(void)n;}void V(sem_t&sem){int n = sem_post(&sem);assert(n==0);(void)n;}
public:RingQueue(const int&cap = gcap):_queue(cap),_cap(cap){int n = sem_init(&_spaceSem,0,_cap);assert(n == 0);n = sem_init(&_dataSem, 0, 0);assert(n==0);  _productorStep = _consumerStep = 0;pthread_mutex_init(&_pmutex,nullptr);pthread_mutex_init(&_cmutex,nullptr);}void Push(const T&in){P(_spaceSem);//申请到了空间信号量,意味着我们一定能进行正常的生产pthread_mutex_lock(&_pmutex);_queue[_productorStep++] = in;_productorStep%=_cap;pthread_mutex_unlock(&_pmutex);V(_dataSem);}void Pop(T *out){P(_dataSem);pthread_mutex_lock(&_cmutex);*out = _queue[_consumerStep++];_consumerStep%=_cap;pthread_mutex_unlock(&_cmutex);V(_spaceSem);}~RingQueue(){sem_destroy(&_spaceSem);sem_destroy(&_dataSem);pthread_mutex_destroy(&_pmutex);pthread_mutex_destroy(&_cmutex);}
private:std::vector<T> _queue;int _cap;sem_t _spaceSem;sem_t  _dataSem;int _productorStep;int _consumerStep;pthread_mutex_t _pmutex;pthread_mutex_t _cmutex;
};

Main.cc

#include "RingQueue.hpp"
#include "Task.hpp"
#include <pthread.h>
#include <ctime>
#include <cstdlib>
#include <sys/types.h>
#include <unistd.h>
std::string SelfName()
{char name[128];snprintf(name,sizeof(name),"thread[%0x%x]",pthread_self());return name;
}
void*ProductorRoutine(void*rq)
{RingQueue<Task>*ringqueue = static_cast<RingQueue<Task>*>(rq);while(true){int x = rand()%10;int y = rand()%5;char op = oper[rand()%oper.size()];Task t(x,y,op,mymath);//生产任务ringqueue->Push(t);std::cout<<SelfName()<<",生产者派发了一个任务:"<<t.toTaskString()<<std::endl;//sleep(1);}
}
void*ConsumerRoutine(void*rq)
{RingQueue<Task>*ringqueue = static_cast<RingQueue<Task>*>(rq);while(true){Task t;//消费任务ringqueue->Pop(&t);std::string result = t();std::cout<<SelfName()<<",消费者消费了一个任务:"<<result<<std::endl;sleep(1);}
}
int main()
{srand((unsigned int) time(nullptr)^getpid()^pthread_self()^0x7432);RingQueue<Task>*rq = new RingQueue<Task>();pthread_t p[4],c[8];for(int i = 0;i<4;i++) pthread_create(p+i,nullptr,ProductorRoutine,rq);for(int i = 0 ;i<8;i++) pthread_create(c+i,nullptr,ConsumerRoutine,rq);for(int i = 0;i<4;i++) pthread_join(p[i],nullptr);for(int i = 0 ;i<8;i++) pthread_join(c[i],nullptr);return 0;
}

总结

多生产多消费的意义:不管是环形队列还是阻塞队列,多线程的意义在于构建or获取任务是要花时间的,效率比较低,当消费的时候也是要花时间的,不单单只是拿出来就行了,所以多生产多消费的时候的意义在于生产之前,消费之后,处理任务获取任务的时候本身也是要花费时间的,可以在生产之前与消费之后让线程并行执行。

条件变量是一种同步机制,它允许线程等待某个条件的发生,通常与互斥锁一起使用。而信号量是一种计数器,它可以用于控制对共享资源的访问;如果想让每一刻只有一个线程访问共享资源,可以使用条件变量。但如果需要允许多个线程并发访问共享资源的不同区域,则可以使用信号量

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

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

相关文章

最新动态 | 大势智慧参加广东省应急测绘保障与安全生产演练

4月20日&#xff0c;2023年度广东省应急测绘保障与安全生产演练在台山市赤溪镇鱼塘湾举行。本次演练由广东自然资源厅主办&#xff0c;广东省国土资源测绘院、江门市自然资源局和台山市人民政府承办。在省市各指导单位与参演单位的多方协同与指挥下&#xff0c;应急测绘保障与安…

【三十天精通Vue 3】第十四天 Vue 3 的单元测试详解

✅创作者&#xff1a;陈书予 &#x1f389;个人主页&#xff1a;陈书予的个人主页 &#x1f341;陈书予的个人社区&#xff0c;欢迎你的加入: 陈书予的社区 &#x1f31f;专栏地址: 三十天精通 Vue 3 文章目录 引言一、为什么要进行单元测试1.1 单元测试的概念1.2 单元测试的优…

ctfshow_WEB_web2 wp

前言 写这个是因为。。。我想摆烂&#xff0c;就去从最简单的题开始做了&#xff0c;想着交一道题是一道嘛&#xff0c;总之觉得这样做很适合欺骗安慰自己&#xff08;逃 然后我发现我错了&#xff0c;我第二道题就做了好久还没做出来&#xff0c;甚至最后去点开了hint…… ps…

Java网络编程系列之NIO

Java网络编程系列之NIO 1.Java NIO概述1.1 阻塞IO1.2 非阻塞IO1.3 NIO概述1.3.1 Channels1.3.2 Buffer1.3.3 Selector 2.Java NIO(Channel)2.1Channel概述2.2 Channel实现2.3 FileChannel 介绍与示例2.4 FileChannel 操作详解2.4.1 打开FileChannel2.4.2 从FileChannel读取数据…

自定义测试平台搭建

体验地址&#xff1a;TestManagePlatform 首次加载会比较慢... 功能点 1.数据工具生成&#xff0c;增删改查 2.测试用例以及测试套件生成&#xff0c;测试执行测试基础用例增删改查。 3.Jacoco 代码增量扫描 4.文章管理 欢迎私聊&#xff0c;支撑自定义开发。

Java基础(十)字符串相关类

1 字符串相关类之不可变字符序列&#xff1a;String 1.1 String的特性 java.lang.String 类代表字符串。Java程序中所有的字符串文字&#xff08;例如"hello" &#xff09;都可以看作是实现此类的实例。 字符串是常量&#xff0c;用双引号引起来表示。它们的值在创…

对数据结构的初步认识

前言: 牛牛开始更新数据结构的知识了.本专栏后续会分享用c语言实现顺序表,链表,二叉树,栈和队列,排序算法等相关知识,欢迎友友们互相学习,可以私信互相讨论哦! &#x1f388;个人主页:&#x1f388; :✨✨✨初阶牛✨✨✨ &#x1f43b;推荐专栏: &#x1f354;&#x1f35f;&a…

Allegro PCB后处理

Allegro PCB后处理&#xff0c;主要是完成线路设计以后&#xff0c;输出生产文件之前的处理。这是看教程做的记录&#xff0c;方便以后自己参考。 教程&#xff1a; [小哥Cadence Allegro 132讲字幕版PCB视频教程]_哔哩哔哩_bilibili 感觉关键是多看右边Options菜单&#xff0…

Simulation Extractable Versions of Groth’s zk-SNARK Revisited学习笔记

1. 引言 等人2020年论文《Simulation Extractable Versions of Groth’s zk-SNARK Revisited》&#xff0c;开源代码实现见&#xff1a; https://github.com/Baghery/ABPR22&#xff08;Rust&#xff0c;基于arkworks开发。使用了Multi-Scalar Multiplication (MSM)技术来优化…

linux下使用ftp下载服务器数据

首先确认服务器地址 比如我要从uniprot获取数据&#xff0c;需要先ping服务器地址&#xff1a; 可见&#xff0c;ftp地址不需要前面的https 匿名登录 匿名&#xff1a;anonymous 密码&#xff1a;任意邮箱&#xff0c;如123163.com 这里的传输模式我使用的是二进制&#xff…

Revit进入Unity教程

一、背景 小伙伴们是否有Revit进入Unity交互的需求呢&#xff1f; 二、实现功能 1.Revit进入Unity,包含模型属性&#xff0c;材质&#xff0c;轻量化等 2.实现BIM构件点选&#xff0c;高亮&#xff0c;属性展示 3.实现BIM模型分层显示&#xff0c;爆炸等效果 学习教程&…

xilinx zynq+vitis实现命令行编译输出xsa以及bin文件

执行菜单命令【开始】—【所有程序】—【Xilinx Design Tools】—【Vivado2020.1】—【Vivado2020.1Tcl Shell】,弹出命令界面 或者cmd命令下输入call D:\soft_install\vivado2020.1\Vivado\2020.1\bin\vivado.bat -mode tcl 2.输入打开工程指令&#xff1a; open_project …

SpringBoot整合Redis,一篇带你入门使用Redis

本文介绍如何将Redis整合到SpringBoot项目中&#xff0c;以及如何配置、封装和使用 文章目录 前言环境搭建项目结构添加依赖 Module封装RedisConfig配置封装常见操作为ServiceRedisServiceRedisLockUtil 测试 前言 参考链接&#xff1a; 英文官网链接中文官网链接Redis githu…

【Matlab】基于紧格式动态线性化的无模型自适应控制

例题来源&#xff1a;侯忠生教授的《无模型自适应控制&#xff1a;理论与应用》&#xff08;2013年科学出版社&#xff09;。 对应书本 4.2 单输入单输出系统(SISO)紧格式动态线性化(CFDL)的无模型自适应控制(MFAC) 例题4.1 题目要求 matlab代码 clc; clear all;%% 期望轨迹…

mybatis-plus的代码生成器的应用

目录 1.工程引入mybatis-plus3-generator代码生成器2.只需要关注mybatis-plus-config.properties然后生成代码3.分别添加依赖解决报错4.加入其它配置然后测试效果 3.4版本 1.工程引入mybatis-plus3-generator代码生成器 mybatis-plus3-generator放入项目工程中&#xff0c;父工…

Java8函数式编程(Lambda表达式,Stream流,Optional)

一.函数式编程思想 面向对象思想主要是关注对象能完成什么事情&#xff0c;函数式编程思想就像函数式&#xff0c;主要是针对数据操作&#xff1b;代码简洁容易理解&#xff0c;方便于并发编程&#xff0c;不需要过分关注线程安全问题 二.lambda表达式 1.概念 lambda表达式…

COMSOL锂离子电池仿真技术与应用

背景&#xff1a; 随着各国燃油车禁售时间表的推出&#xff0c;新能源汽车的地位愈发稳固。而锂离子电池作为电动车的核心动力源&#xff0c;也越来越受到市场的追捧。锂离子电池在制作过程中涉及正极、电解液、负极、隔膜等材料的选取与匹配&#xff0c;极片设计参数的选择等…

如何开发一款用户体验优秀的语音交友app?

在数字时代&#xff0c;人们越来越依赖智能手机上的应用程序来与他人进行交流。其中&#xff0c;语音交友app成为了最受欢迎的应用之一。然而&#xff0c;开发一款成功的语音交友app需要深入了解用户需求与体验。本文将探讨如何开发一款用户体验优秀的语音交友app。 着眼于用户…

[oeasy]python0135_变量名与下划线_dunder_声明与赋值

变量定义 回忆上次内容 变量 就是 能变的量上次研究了 变量标识符的 规则 第一个字符 应该是 字母或下划线合法的标识符可以包括 大小写字母数字下划线 还研究了字符串(str)的函数 isidentifier查询字符串 是否为合法标识符 最后发现 这个isidentifier函数有时候不好使&…

记录bingAI解答pyjwt参数和头部的问题

python jwt.encode()函数的参数是哪些 正在搜索: python jwt.encode()函数的参数 正在为你生成答案… 已收到消息. 在Python中&#xff0c;jwt.encode()函数的参数有三个&#xff1a;第一个是payload&#xff0c;主要用来存放有效的信息&#xff0c;例如用户名&#xff0c;过期…