基于阻塞队列下的生产者消费者模型(多线程)

目录

  • 一、生产者消费者模型
    • 1.1 为何要使用生产者消费者模型
    • 1.2 生产者消费者模型优点
    • 1.3 基于BlockingQueue的生产者消费者模型
    • 1.4 C++用queue模拟阻塞队列的生产消费模型
      • 1.4.1 makefile
      • 1.4.2 BlockQueue.hpp
      • 1.4.3 LockGuard.hpp
      • 1.4.4 Task.hpp
      • 1.4.5 main.cc
  • 二、Linux多线程内容一览图

一、生产者消费者模型

321原则(便于自己记忆)

1.1 为何要使用生产者消费者模型

生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。这个阻塞队列就是用来给生产者和消费者解耦的。

1.2 生产者消费者模型优点

1、解耦
2、支持并发(效率高)
3、支持忙闲不均

1.3 基于BlockingQueue的生产者消费者模型

在多线程编程中阻塞队列(Blocking Queue)是一种常用于实现生产者和消费者模型的数据结构。其与普通的队列区别在于,当队列为空时,从队列获取元素的操作将会被阻塞,直到队列中被放入了元素;当队列满时,往队列里存放元素的操作也会被阻塞,直到有元素被从队列中取出(以上的操作都是基于不同的线程来说的,线程在对阻塞队列进程操作时会被阻塞)

在这里插入图片描述

1.4 C++用queue模拟阻塞队列的生产消费模型

单生产者,单消费者

1.4.1 makefile

cp:main.ccg++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:rm -f cp

1.4.2 BlockQueue.hpp


#include <iostream>
using namespace std;
#include <pthread.h>#include <queue>
#include "LockGuard.hpp"static const size_t DefaultCapacity = 5;template <class T>
class BlockQueue
{
public:BlockQueue(const size_t capacity = DefaultCapacity): _capacity(capacity){// 初始化互斥锁和环境变量pthread_mutex_init(&_mtx, nullptr);pthread_cond_init(&_Full, nullptr);pthread_cond_init(&_Empty, nullptr);}~BlockQueue(){// 销毁互斥锁和环境变量pthread_mutex_destroy(&_mtx);pthread_cond_destroy(&_Full);pthread_cond_destroy(&_Empty);}// void Push(const T &in)// {//     // 为啥要先加锁,再判断size与_capacity的大小关系,//     // 因为判断也是访问临界资源,所以要先加锁,保证安全//     pthread_mutex_lock(&_mtx);//     // 临界区//     // 为什么要while循环判断,因为条件变量可能存在伪唤醒//     // 的情况,即可能条件变量并不满足条件,但是由于一些错//     // 误导致条件变量被唤醒了,或者pthread_cond_wait//     // 函数调用失败了,这时并不是直接往下执行,而是再循环//     // 判断条件是否已经满足//     while (_bq.size() == _capacity)//     {//         // 队列满了就到_Full的条件变量下等待,//         pthread_cond_wait(&_Full, &_mtx);//     }//     // 来到这里说明阻塞队列一定满足Push的条件,即阻塞队列//     // 一定不是满的,所以可以直接Push//     _bq.push(in);//     // 既然插入了数据,那么队列就一定有数据,所以这时就可//     // 以通知消费者来消费了,消费者是在_Empty的条件变量下//     // 等待的,所以此时可以向条件变量_Empty发送就绪信号//     pthread_cond_signal(&_Empty);//     printf("0x%x",pthread_self());//     cout <<":生产者生成了一个任务: " << in._x << " " << in._op //         << " " << in._y << " = ?" << endl;//     pthread_mutex_unlock(&_mtx);// }void Push(const T &in){// 为啥要先加锁,再判断size与_capacity的大小关系,// 因为判断也是访问临界资源,所以要先加锁,保证安全//RAII风格的锁LockGuard lock(&_mtx);// 临界区// 为什么要while循环判断,因为条件变量可能存在伪唤醒// 的情况,即可能条件变量并不满足条件,但是由于一些错// 误导致条件变量被唤醒了,或者pthread_cond_wait// 函数调用失败了,这时并不是直接往下执行,而是再循环// 判断条件是否已经满足while (_bq.size() == _capacity){// 队列满了就到_Full的条件变量下等待,pthread_cond_wait(&_Full, &_mtx);}// 来到这里说明阻塞队列一定满足Push的条件,即阻塞队列// 一定不是满的,所以可以直接Push_bq.push(in);// 既然插入了数据,那么队列就一定有数据,所以这时就可// 以通知消费者来消费了,消费者是在_Empty的条件变量下// 等待的,所以此时可以向条件变量_Empty发送就绪信号pthread_cond_signal(&_Empty);printf("0x%x",pthread_self());cout <<":生产者生成了一个任务: " << in._x << " " << in._op << " " << in._y << " = ?" << endl;}void Print(const T& out){int x = out._x;int y = out._y;int z = 0;switch (out._op){case '+':{z = x + y;break;}case '-':{z = x - y;break;}case '*':{z = x * y;break;}case '/':{z = x / y;break;}case '%':{z = x % y;break;}default:{cout << "运算符错误!!!" << endl;exit(1);}}printf("0x%x",pthread_self());cout <<":消费者消费了一个任务 : " << out._x << " " << out._op << " " << out._y << " = " << z << endl;}// T Pop()// {//     pthread_mutex_lock(&_mtx);//     // 循环判断,原因用上面的Push//     while (_bq.empty())//     {//         pthread_cond_wait(&_Empty, &_mtx);//     }//     // 来到这里说明队列一定不为空,此时就可以Pop一个数据了//     T out = _bq.front();//     _bq.pop();//     Print(out);//     // 因为我们刚刚pop了一个数据,所以阻塞队列一定不是满//     // 的,此时可以通知生产者来生产数据了//     pthread_cond_signal(&_Full);//     pthread_mutex_unlock(&_mtx);//     return out;// }T Pop(){//RAII风格的锁LockGuard lock(&_mtx);// 循环判断,原因用上面的Pushwhile (_bq.empty()){pthread_cond_wait(&_Empty, &_mtx);}// 来到这里说明队列一定不为空,此时就可以Pop一个数据了T out = _bq.front();_bq.pop();Print(out);// 因为我们刚刚pop了一个数据,所以阻塞队列一定不是满// 的,此时可以通知生产者来生产数据了pthread_cond_signal(&_Full);return out;}private:queue<T> _bq;          // 阻塞队列size_t _capacity;      // 阻塞队列的容量pthread_mutex_t _mtx;  // 互斥锁pthread_cond_t _Full;  // 表示阻塞队列已经满了的条件变量pthread_cond_t _Empty; // 表示阻塞队列已经空了的条件变量
};

1.4.3 LockGuard.hpp

#include <pthread.h>class Mtx
{
public:Mtx(pthread_mutex_t* mtx):_mtx(mtx){}~Mtx(){}void Lock(){pthread_mutex_lock(_mtx);}void Unlock(){pthread_mutex_unlock(_mtx);}private:pthread_mutex_t* _mtx;
};//RAII风格的锁
//创建这个对象就是上锁,析构这个对象就是解锁
class LockGuard
{
public:LockGuard(pthread_mutex_t* mtx):_mtx(mtx)//单参数的构造函数支持隐式类型的转换{_mtx.Lock();}~LockGuard(){_mtx.Unlock();}private:Mtx _mtx;
};

1.4.4 Task.hpp

class Task
{
public:Task(const int x,const int y,const char op):_x(x),_y(y),_op(op){}public:int _x;int _y;char _op;
};

1.4.5 main.cc

#include <iostream>
using namespace std;
#include <ctime>
#include <unistd.h>#include "BlockQueue.hpp"
#include "Task.hpp"void *producter(void *args)
{BlockQueue<Task> *bq = static_cast<BlockQueue<Task> *>(args);while (true){int x = 0;int y = 0;char op;cout << "Please input the first num : ";cin >> x;cout << "Please input the second num : ";cin >> y;cout << "Please input the operator : ";cin >> op;Task t(x, y, op);bq->Push(t);usleep(1000);}return nullptr;
}void *consumer(void *args)
{BlockQueue<Task> *bq = static_cast<BlockQueue<Task> *>(args);while (true){sleep(1);Task out = bq->Pop();}return nullptr;
}int main()
{srand((unsigned int)time(nullptr));pthread_t c, p;BlockQueue<int> bq;pthread_create(&p, nullptr, producter, &bq);pthread_create(&c, nullptr, consumer, &bq);pthread_join(p, nullptr);pthread_join(c, nullptr);return 0;
}

二、Linux多线程内容一览图

在这里插入图片描述

以上就是基于阻塞队列下实现生产者消费者模型啦,你学会了吗?如果感觉到有所收获的话,那就点点小心心,点点关注呗,后期还会持续更新Linux系统编程的相关知识哦,我们下期见!!!

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

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

相关文章

1854_bash中利用管道进行批量参数传递以及由此实现简单的代码行数统计

Grey 全部学习内容汇总&#xff1a; GreyZhang/bash_basic: my learning note about bash shell. (github.com) 1854_bash中的参数传递以及利用bash进行简单的代码行数统计 有时候需要处理多个文件&#xff0c;把每一个文件作为参数传递给某一个程序。这时候可以用到 xargs&…

XSKY星辰天合星海架构荣获 IT168 “2023 年度技术卓越奖”

近日&#xff0c;"2023 年度技术卓越奖"获奖名单公布&#xff0c;XSKY 星辰天合的星海架构&#xff08;XSEA&#xff0c;极速全共享架构&#xff09;获得行业 CIO/CTO 大咖、技术专家及 IT 媒体三方认可&#xff0c;成功入选&#xff01; “技术卓越奖”评选由国内著…

KMP入门级别算法详解--终于解决了(next数组详解)

对于正常的字符串模式匹配&#xff0c;主串长度为m&#xff0c;子串为n&#xff0c;时间复杂度会到达O&#xff08;m*n&#xff09;&#xff0c;而如果用KMP算法&#xff0c;复杂度将会减少线型时间O&#xff08;mn&#xff09;。 设主串为ptr"ababaaababaa";&#…

双非本科求职_面试经验

前言 继上次发实习面经已经过去一个多快两个月&#xff0c;这期间又面试了不少&#xff0c;但由于赶上秋招因此一直没写后续&#xff0c;今天做个回忆总结。由于菜鸟脑容量本来就不咋地&#xff0c;所以当初没写导致现在忘了好多&#xff0c;因此就写那些印象比较深的吧。交代…

【C++进阶02】多态

一、多态的概念及定义 1.1 多态的概念 多态简单来说就是多种形态 同一个行为&#xff0c;不同对象去完成时 会产生出不同的状态 多态分为静态多态和动态多态 静态多态指的是编译时 在程序编译期间确定了程序的行为 比如&#xff1a;函数重载 动态多态指的是运行时 在程序运行…

变限积分求导(带参,极限)

方法 一般形 带参数方程形 带极限型

IDEA 中 Tomcat 日志乱码

1、服务器输出乱码 修改 File -> settings -> Editor -> General ->Console 中&#xff0c;utf-8改为GBK&#xff0c;反之改成utf-8 2、Tomcat Localhost Log 或者 Tomcat Catalina Log乱码 进入Tomcat 中的conf文件中的logging.properties 哪个有问题改哪个&…

Ubuntu:VS Code上C++的环境配置

使用 VSCode 开发 C/C 程序 , 涉及到 工作区的.vscode文件夹下的3个配置文件&#xff08;均可以手动创建&#xff09; : ① tasks.json : 编译器构建 配置文件 ; ② launch.json : 调试器设置 配置文件 ; ③ c_cpp_properties.json : 编译器路径和智能代码提示 配置文件 ; …

竞赛保研 基于GRU的 电影评论情感分析 - python 深度学习 情感分类

文章目录 1 前言1.1 项目介绍 2 情感分类介绍3 数据集4 实现4.1 数据预处理4.2 构建网络4.3 训练模型4.4 模型评估4.5 模型预测 5 最后 1 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 基于GRU的 电影评论情感分析 该项目较为新颖&#xff0c;适合作为竞…

uniapp怎么动态渲染导航栏的title?

直接在接口请求里面写入以下&#xff1a; 自己要什么参数就写什么参数 本人仅供参考&#xff1a; this.name res.data.data[i].name; console.log(名字, res.data.data[i].name); uni.setNavigationBarTitle({title: this.name}) 效果&#xff1a;

界面控件DevExpress v23.2全新发布 - 官宣正式支持.NET 8

DevExpress拥有.NET开发需要的所有平台控件&#xff0c;包含600多个UI控件、报表平台、DevExpress Dashboard eXpressApp 框架、适用于 Visual Studio的CodeRush等一系列辅助工具。屡获大奖的软件开发平台DevExpress 今年第一个重要版本v23.1正式发布&#xff0c;该版本拥有众多…

计算机网络(5):运输层

这一章应该是整个计算机网络对我们来说最重要的&#xff0c;也是用的最多的一部分。 运输层协议 进程之间的通信 从通信和信息处理的角度看&#xff0c;运输层向它上面的应用层提供通信服务&#xff0c;它属于面向通信部分的最高层&#xff0c;同时也是用户功能中的最低层。…