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

2024-08-27 17:20

本文主要是介绍基于信号量和环形队列的生产者消费者模型,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

在这里插入图片描述

文章目录

  • 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.chinasem.cn/article/1112293

相关文章

大模型研发全揭秘:客服工单数据标注的完整攻略

在人工智能(AI)领域,数据标注是模型训练过程中至关重要的一步。无论你是新手还是有经验的从业者,掌握数据标注的技术细节和常见问题的解决方案都能为你的AI项目增添不少价值。在电信运营商的客服系统中,工单数据是客户问题和解决方案的重要记录。通过对这些工单数据进行有效标注,不仅能够帮助提升客服自动化系统的智能化水平,还能优化客户服务流程,提高客户满意度。本文将详细介绍如何在电信运营商客服工单的背景下进行

hdu1180(广搜+优先队列)

此题要求最少到达目标点T的最短时间,所以我选择了广度优先搜索,并且要用到优先队列。 另外此题注意点较多,比如说可以在某个点停留,我wa了好多两次,就是因为忽略了这一点,然后参考了大神的思想,然后经过反复修改才AC的 这是我的代码 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<

Andrej Karpathy最新采访:认知核心模型10亿参数就够了,AI会打破教育不公的僵局

夕小瑶科技说 原创  作者 | 海野 AI圈子的红人,AI大神Andrej Karpathy,曾是OpenAI联合创始人之一,特斯拉AI总监。上一次的动态是官宣创办一家名为 Eureka Labs 的人工智能+教育公司 ,宣布将长期致力于AI原生教育。 近日,Andrej Karpathy接受了No Priors(投资博客)的采访,与硅谷知名投资人 Sara Guo 和 Elad G

Retrieval-based-Voice-Conversion-WebUI模型构建指南

一、模型介绍 Retrieval-based-Voice-Conversion-WebUI(简称 RVC)模型是一个基于 VITS(Variational Inference with adversarial learning for end-to-end Text-to-Speech)的简单易用的语音转换框架。 具有以下特点 简单易用:RVC 模型通过简单易用的网页界面,使得用户无需深入了

透彻!驯服大型语言模型(LLMs)的五种方法,及具体方法选择思路

引言 随着时间的发展,大型语言模型不再停留在演示阶段而是逐步面向生产系统的应用,随着人们期望的不断增加,目标也发生了巨大的变化。在短短的几个月的时间里,人们对大模型的认识已经从对其zero-shot能力感到惊讶,转变为考虑改进模型质量、提高模型可用性。 「大语言模型(LLMs)其实就是利用高容量的模型架构(例如Transformer)对海量的、多种多样的数据分布进行建模得到,它包含了大量的先验

图神经网络模型介绍(1)

我们将图神经网络分为基于谱域的模型和基于空域的模型,并按照发展顺序详解每个类别中的重要模型。 1.1基于谱域的图神经网络         谱域上的图卷积在图学习迈向深度学习的发展历程中起到了关键的作用。本节主要介绍三个具有代表性的谱域图神经网络:谱图卷积网络、切比雪夫网络和图卷积网络。 (1)谱图卷积网络 卷积定理:函数卷积的傅里叶变换是函数傅里叶变换的乘积,即F{f*g}

秋招最新大模型算法面试,熬夜都要肝完它

💥大家在面试大模型LLM这个板块的时候,不知道面试完会不会复盘、总结,做笔记的习惯,这份大模型算法岗面试八股笔记也帮助不少人拿到过offer ✨对于面试大模型算法工程师会有一定的帮助,都附有完整答案,熬夜也要看完,祝大家一臂之力 这份《大模型算法工程师面试题》已经上传CSDN,还有完整版的大模型 AI 学习资料,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费

【生成模型系列(初级)】嵌入(Embedding)方程——自然语言处理的数学灵魂【通俗理解】

【通俗理解】嵌入(Embedding)方程——自然语言处理的数学灵魂 关键词提炼 #嵌入方程 #自然语言处理 #词向量 #机器学习 #神经网络 #向量空间模型 #Siri #Google翻译 #AlexNet 第一节:嵌入方程的类比与核心概念【尽可能通俗】 嵌入方程可以被看作是自然语言处理中的“翻译机”,它将文本中的单词或短语转换成计算机能够理解的数学形式,即向量。 正如翻译机将一种语言

poj 3190 优先队列+贪心

题意: 有n头牛,分别给他们挤奶的时间。 然后每头牛挤奶的时候都要在一个stall里面,并且每个stall每次只能占用一头牛。 问最少需要多少个stall,并输出每头牛所在的stall。 e.g 样例: INPUT: 51 102 43 65 84 7 OUTPUT: 412324 HINT: Explanation of the s

poj 2431 poj 3253 优先队列的运用

poj 2431: 题意: 一条路起点为0, 终点为l。 卡车初始时在0点,并且有p升油,假设油箱无限大。 给n个加油站,每个加油站距离终点 l 距离为 x[i],可以加的油量为fuel[i]。 问最少加几次油可以到达终点,若不能到达,输出-1。 解析: 《挑战程序设计竞赛》: “在卡车开往终点的途中,只有在加油站才可以加油。但是,如果认为“在到达加油站i时,就获得了一