Linux实现生产者消费者模型(基于阻塞队列)

server/2025/3/31 2:14:37/

目录

概念及优势

代码实现


概念及优势

生产者消费者模型是一种用于线程同步的模型,在这个模型中有两种角色,生产者生产数据,消费者消费数据。有三种关系,生产者与生产者,消费者与消费者,生产者与消费者。还有一个交易场所。

超市就是生活中最常见的生产者消费者模型,工厂生产商品,超市充当缓冲区,消费者去超市消费同时取走超市中的商品。

超市作为缓冲区,起到了很重要的作用,试想如果没有超市,那消费者想购物只能去找工厂,还要等待工厂将商品生产出来,同时工厂也不能持续生产商品,因为没有足够的空间存放,而超市作为缓冲区解决了这些问题,消费者不用去工厂阻塞等待商品生产,工厂也可以一直生产,只要超市还能放得下。

从上面的例子可以看出生产者消费者模型的优点,将生产者和消费者解耦,支持并发,支持忙闲不均。

再来讨论一下这三者间的关系

消费者与消费者不能同时向交易场所写数据,生产者与生产者也不能同时向交易场所拿数据,这两者都是互斥且竞争的关系。而生产者不能与消费者同时拿或写数据,这两者属于互斥且同步的关系。

交易场所的问题,交易场所为空时消费者不能消费,交易场所满时生产者不能生产。

代码实现

基于阻塞队列来实现生产者消费者模型,阻塞队列与普通队列的区别在于,阻塞队列在队列为空是pop操作会阻塞,队列为满时,push操作会阻塞。

//block_queue.hpp
#include <queue>
#include <pthread.h>namespace my
{template<class T>class block_queue{public:block_queue(size_t max_size = 1000):_max_size(max_size){pthread_mutex_init(&_mutex, nullptr);pthread_cond_init(&_consumer_cond, nullptr);pthread_cond_init(&_producer_cond, nullptr);}  bool empty() const{return _b_queue.empty();}bool full() const{return _b_queue.size() == _max_size;}//生产者void push(const T& data){pthread_mutex_lock(&_mutex);    //加锁//循环判断以防止虚假唤醒//如果用if判断,试想这种场景//线程a在等待,然后被唤醒了,既然被唤醒说明此时应该是不为满//但是被唤醒后还需要竞争锁(因为等待会释放锁),只有线程a再次持有锁//才会醒来执行后面的代码//但是线程a有可能并没有立马拿到锁,被另一个生产者线程b拿到锁//并且b此时判断也是不为满//那么b会向后执行,会向队列里push数据//这就有问题了,在这之后线程a拿到了锁,向后执行,但现在已经是满队列了while(full()){//队列满了,生产者等待//等待必须解锁,否则其他线程都拿不到锁//条件变量等待的条件一定属于临界资源,//所以pthread_cond_wait一定属于临界区代码//所以pthread_cond_wait这个函数在内部一定会解锁来防止死锁//被唤醒后会竞争锁pthread_cond_wait(&_producer_cond, &_mutex);    }_b_queue.push(data);                   //入队std::cout << "生产数据" << data << std::endl;pthread_mutex_unlock(&_mutex);         //解锁pthread_cond_signal(&_consumer_cond);  //唤醒消费者std::cout << "唤醒消费者" << std::endl;}const T& pop(){pthread_mutex_lock(&_mutex);    //加锁//循环判断以防止虚假唤醒while(empty()){pthread_cond_wait(&_consumer_cond, &_mutex);    }T& ret = _b_queue.front();std::cout << "消费数据" << ret << std::endl;_b_queue.pop();                        //出队                  pthread_mutex_unlock(&_mutex);         //解锁pthread_cond_signal(&_producer_cond);  //唤醒生产者std::cout << "唤醒生产者" << std::endl;return ret;}~block_queue(){pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_consumer_cond);pthread_cond_destroy(&_producer_cond);}  size_t size() const{return _b_queue.size();}private:std::queue<T> _b_queue;            //底层容器size_t _max_size;                  //队列最大容量pthread_mutex_t _mutex;            //给队列加锁pthread_cond_t _consumer_cond;     //消费者条件变量pthread_cond_t _producer_cond;     //生产者条件变量};}

main函数

#include <iostream>
#include <memory>
#include <unistd.h>
#include "block_queue.hpp"using data = int;
void* producer(void* _bq)
{my::block_queue<data>& bq = *(my::block_queue<data>*)_bq;data val = 0;while (true){//使得生产者总是慢于消费者,这样队列一直都只会有一个元素//sleep(2);bq.push(val);val++;}
}void* consumer(void* _bq)
{my::block_queue<data>& bq = *(my::block_queue<data>*)_bq;data val = 0;while (true){val = bq.pop();}
}void test()
{pthread_t tid_consumer1 = 0, tid_producer1 = 0;pthread_t tid_consumer2 = 1, tid_producer2 = 0;pthread_t tid_consumer3 = 2, tid_producer3 = 0;std::shared_ptr<my::block_queue<int>> bq(std::make_shared<my::block_queue<int>>(5));pthread_create(&tid_producer1, nullptr, producer, &(*bq));pthread_create(&tid_producer2, nullptr, producer, &(*bq));pthread_create(&tid_producer3, nullptr, producer, &(*bq));pthread_create(&tid_consumer1, nullptr, consumer, &(*bq));pthread_create(&tid_consumer2, nullptr, consumer, &(*bq));pthread_create(&tid_consumer3, nullptr, consumer, &(*bq));pthread_join(tid_producer1, nullptr);pthread_join(tid_producer2, nullptr);pthread_join(tid_producer3, nullptr);pthread_join(tid_consumer1, nullptr);pthread_join(tid_consumer2, nullptr);pthread_join(tid_consumer3, nullptr);}int main()
{test();return 0;
}

关于生产者消费者模型如何提高性能的讨论

可以看到我所实现的生产者消费者模型在消费者,生产者,他们访问交易场所时都是有锁的,也就是说每个人,不管是生产者还是消费者在访问交易场所时都是串行的而不是并行的,也许这时你会疑惑这如何能提高性能。

实际上,生产者生产的数据是有来源的,有可能生产者的数据是通过访问数据库得到的,可能是通过访问网络得到的,同样消费者是需要对拿到的数据进行处理的,而无论是生产者去获得数据还是消费者对数据进行处理,都是要耗时的,甚至这整个场景最耗时其实就是数据获得和数据处理。

而生产者消费者模型可以使这部分并行发生,所以可以提高效率。

之后也会给出生产者和消费者并发访问交易场所的代码,但不是基于阻塞队列的,而是基于环形队列的。


http://www.ppmy.cn/server/179869.html

相关文章

d2025327

目录 一、sql-连续出现的数字 二、三数之和 三、125. 验证回文串 - 力扣&#xff08;LeetCode&#xff09; 180. 连续出现的数字 - 力扣&#xff08;LeetCode&#xff09; 一、sql-连续出现的数字 找出连续出现三次以上的数字&#xff0c;并且需要去重 连续三次可以用三个…

合宙780E开发学习-LUATOS-SOC云编译自定义固件

登录https://luatos.com 点击登录&#xff0c;使用合宙erp账号登录即可 点击右上角构建&#xff0c;点击右上角菜单新构建&#xff0c;自定义构建名称&#xff0c;可新建多个 勾选想要的组件 点击右上角保存修改&#xff0c;只有点击准备就绪&#xff08;注意&#xff1a;一定…

Layotto 是一款使用 Golang 开发的应用运行时,旨在帮助开发人员快速构建云原生应用

前言 大家好&#xff0c;我是老马。 sofastack 其实出来很久了&#xff0c;第一次应该是在 2022 年左右开始关注&#xff0c;但是一直没有深入研究。 最近想学习一下 SOFA 对于生态的设计和思考。 sofaboot 系列 SOFABoot-00-sofaboot 概览 SOFABoot-01-蚂蚁金服开源的 s…

【机器学习基础 4】 Pandas库

一、Pandas库简介 Pandas 是一个开源的 Python 数据分析库&#xff0c;主要用于数据清洗、处理、探索与分析。其核心数据结构是 Series&#xff08;一维数据&#xff09;和 DataFrame&#xff08;二维表格数据&#xff09;&#xff0c;可以让我们高效地操作结构化数据。Pandas …

Axure设计之中继器表格——拖动列调整位置教程(中继器)

一、原理介绍 实现表格列的拖动排序&#xff0c;主要依赖Axure的动态面板和中继器两大核心功能&#xff1a; 动态面板交互控制 将表格的列标题封装在动态面板中&#xff0c;通过拖拽事件&#xff08;开始、移动、结束&#xff09;捕捉用户操作 在拖拽过程中实时计算鼠标位置&…

Oracle迁移至华为GaussDB SQL语法和存储过程转换

将Oracle迁移至华为GaussDB时,SQL语法和存储过程的转换是关键步骤之一。Oracle使用的是PL/SQL,而GaussDB主要基于PostgreSQL,使用的是PL/pgSQL。因此,在迁移过程中,需要详细规划和执行,以确保SQL语法和存储过程的兼容性和正确性。以下是详细的转换指南,包括步骤、工具、…

小程序中过滤苹果设备中的表情(即 emoji)

在小程序中过滤苹果设备中的表情&#xff08;即 emoji&#xff09;&#xff0c;通常需要考虑以下几个方面&#xff1a;识别 emoji、处理用户输入、以及在显示或存储时进行过滤。以下是具体的实现思路和步骤&#xff1a; 1. 理解苹果中的表情&#xff08;Emoji&#xff09; 苹果…

数据湖的崛起:从大数据到智能未来的钥匙

文章目录 一、数据湖的本质&#xff1a;从刚性仓库到流动湖泊1.1 传统数据仓库的局限1.2 数据湖的核心优势 二、技术演进&#xff1a;从Hadoop到云原生数据湖2.1 关键发展阶段2.2 云服务商技术对比 三、数据湖核心技术栈3.1 存储层架构3.2 计算引擎选型3.3 元数据管理3.4 数据治…