首页 > 编程语言 >C++多线程:生产者消费者模式

C++多线程:生产者消费者模式

时间:2024-06-15 13:32:08浏览次数:13  
标签:mmutex cnt 互斥 生产者 C++ repo 仓库 产品 多线程

文章目录

一、模式简介

假设你有一个工厂Factory,配有一个仓库Repository,仓库大小为20,要生产200个产品,你有两个工人producer,三个受众群体consumer,应当如何描述这200个产品从生产到消费的全过程?
这一种生活中的问题在多线程程序设计上称为生产者消费者模式,形象的表示了多线程中数据获取、数据存储、数据处理的过程,关键点在于用互斥锁、条件变量等解决数据存取、同时存、同时取之间的冲突。

二、头文件、全局变量

引入头文件,按照问题描述,可以得到计划产品个数和仓库大小两个全局变量。

#include<iostream>
#include<thread>
#include<mutex>
#include<condition_variable>
#include<queue>
using namespace std;

const int kProduceAimSize = 200;	//计划产品个数
const int kRepositorySize = 20;		//仓库大小

2.1 仓库类的设计

2.1.1 关于仓库类的分析

按照上上面提出的问题,我们大致能分析得到以下线索:
工厂描述了问题总体,是一个用于解决问题的类,核心在于用于数据交互的仓库类。
对于仓库的数据管理,应当设计三个互斥量,依次为:存取冲突互斥量、同时存的互斥量、同时取的互斥量。两个条件变量,依次表示为:仓库状态为空、仓库状态为满。

2.1.2 仓库类的设计代码

template <typename _T>
class Repository {		//仓库
public:
	deque<_T> items_buff;
	mutex mmutex;					//生产者和消费者互斥量,解决从仓库中存取的冲突
	mutex mmutex_produce;			//生产者之间的互斥量,解决同时生产同一个产品的冲突
	mutex mmutex_consume;			//消费者之间的互斥量,解决同时消费同一个产品的冲突
	mutex mmutex_print;				//保证由cout打印的提示信息不会中断

	condition_variable repo_full;	//描述仓库为满的条件变量
	condition_variable repo_empty;	//描述仓库为空的条件变量

	size_t cnt_produce;				//生产者目前产生的产品总数
	size_t cnt_consume;				//消费者目前消费的产品总数
	size_t current_size;			//仓库中产品个数
	Repository() {					//初始化
		cnt_produce=0;
		cnt_consume=0;
		current_size = 0;
	}
};

2.2 工厂类的设计

2.2.1 关于工厂类的分析

按照问题描述,工厂由工人和消费者组成,流程上需要完成四个基本任务:
1、生产产品
2、将产品放入仓库
3、从仓库中取出产品
4、消费产品
同时注意工厂类包含一个仓库类实例。

因此工厂类的大致架构如下:
在这里插入图片描述

2.2.2 工厂类的设计代码

a 将产品item放到仓库repo

void PutInto(Repository<_T>& repo, _T item) {
	unique_lock< mutex> lk(repo.mmutex);
	//仓库是一个存取数据的地方,因而对仓库进行操作需要上锁。(解决从仓库中存取的冲突)

	while (repo.current_size == kRepositorySize) {	//如果仓库为满,无法向仓库中继续放
		unique_lock<mutex> pt(repo.mmutex_print);
		cout << "仓库已满,无法放入更多产品,需先消费。" << endl;
		pt.unlock();
		repo.repo_full.wait(lk);	//将取互斥量抛出,等待其他线程通知
	}

	repo.items_buff.push_back(item);
	repo.current_size++;
	repo.repo_empty.notify_all();	//有产品了,唤醒仓库为空情况下的线程
}

b 将产品item从仓库repo取出

_T GetFrom(Repository<_T>& repo) {
	unique_lock<mutex> lk(repo.mmutex);	
	//仓库是一个存取数据的地方,因而对仓库进行操作需要上锁。(解决从仓库中存取的冲突)

	while (repo.current_size == 0) {	//如果仓库为空,等待
		unique_lock<mutex> pt(repo.mmutex_print);
		cout << "无货源,等待..." << endl;
		pt.unlock();
		repo.repo_empty.wait(lk);	//将存取互斥量抛出,等待其他线程通知
	}

	_T data = repo.items_buff.front();
	repo.items_buff.pop_front();
	repo.current_size--;
	repo.repo_full.notify_all();	//从仓库取出了产品,唤醒仓库为满情况下的线程
	return data;
}

c 生产者操作

void ProduceTask() {
 	bool ready_to_exit = false; //线程结束条件
	while (true) {
		unique_lock<mutex> lk(repo.mmutex_produce);	//不能生产同一个产品
		
		if (repo.cnt_produce < kProduceAimSize) {//需要生产(没达到目标)
			repo.cnt_produce++;
			//生产产品假设0.05s
			this_thread::sleep_for(0.05s);	
			_T item = repo.cnt_produce;	//生产产品具体过程的化简
			
			unique_lock<mutex> pt(repo.mmutex_print);
			cout << "生产者id:" << this_thread::get_id() << " as[" << item<<"]" << endl;
			pt.unlock();
			
			PutInto(repo, item);
		}
		else {
			ready_to_exit = true;
		}
		if (ready_to_exit == true)break;
	}
}

d 消费者操作

void ConsumeTask() {
	bool ready_to_exit = false;	//线程结束条件
	while (true){
		unique_lock<mutex> lk(repo.mmutex_consume);	//不能同时消费一个产品
	
		if (repo.cnt_consume < kProduceAimSize) {//需要消费(没达到目标)
			_T item = GetFrom(repo);	//获取需要消费的产品
			repo.cnt_consume++;
			//消费产品代码,假设消费0.06s
			this_thread::sleep_for(0.06s);	//消费过程的化简
			
			unique_lock<mutex> pt(repo.mmutex_print);
			cout << "消费者id:" << this_thread::get_id() << " as[" << item<<"]" << endl;
			pt.unlock();
		}
		else {
			ready_to_exit = true;
		}
		if (ready_to_exit == true)break;
	}
}

2.2.3 主函数代码

int main() {
	cout << "主线程id:" << this_thread::get_id() << endl;
	//一个工厂类
	Factory<int> MyFactory;
	
	//两个生产者
	thread producer1(&Factory<int>::ProduceTask, ref(MyFactory));
	thread producer2(&Factory<int>::ProduceTask, ref(MyFactory));
	
	//三个消费者
	thread consumer1(&Factory<int>::ConsumeTask, ref(MyFactory));
	thread consumer2(&Factory<int>::ConsumeTask, ref(MyFactory));
	thread consumer3(&Factory<int>::ConsumeTask, ref(MyFactory));

	producer1.join();	
	producer2.join();

	consumer1.join();
	consumer2.join();
	consumer3.join();
	return 0;
}

三、运行效果和说明

有概率出现无货源、仓库满两种情况,均能进行正常处理,对于同一次运行,生产者id有两种,消费者id有三种,下面两张截图来自于不同运行。
C++多线程:生产者消费者模式
C++多线程:生产者消费者模式
如果你觉得文章不错,对你有帮助,不妨点个关注,欢迎批评指正,谢谢!

标签:mmutex,cnt,互斥,生产者,C++,repo,仓库,产品,多线程
From: https://blog.csdn.net/2301_81290340/article/details/139700324

相关文章

  • 【C++】类和对象(下)
    【C++】类和对象(下)初始化列表构造时的类型转化static成员概念特性友元友元函数友元类内部类匿名对象总结初始化列表在对类和对象有了基本的认识之后,可以知道在创建对象的时候,编译器通过调用构造函数,给对象中各个成员变量一个合适的初始值。classDate{public: ......
  • 【C++核心编程】菱形继承&虚基类
    多继承多继承的语法:class派生类名:[继承方式1]基类名1,[继承方式2]基类名2,......{派生类新增加的成员};不提倡使用多继承,只有在比较简单和不出现二义性的情况时才使用多继承,能用单一继承解决的问题就不要使用多继承。如果继承的层次很多、关系很复杂,程序的编写、......
  • C#等待多线程任务都执行结束
    有时为了快速处理多个任务,同时启用多个线程执行,需要等待都执行结束后再执行后面的方法,实现方法如下:点击查看代码usingSystem;usingSystem.Threading.Tasks;classProgram{staticasyncTaskMain(string[]args){vartask1=Task.Run(()=>DoWork......
  • 自动化生成C/C++单元测试覆盖率报告!
    覆盖率生成脚本化处理在《生成单元覆盖率》一文中,我们已经可以成功的生成代码覆盖率报告,但是,不知道各位读者有没发现,整个过程是有一定繁杂的,多个命令搭配诸多不同的参数,对于初初接触的人来说,敲一下看一眼,生怕敲错、或者cv大法来来回回好几趟,生成个报告,没个三几分钟都不行;对......
  • C/C++生成单元测试覆盖率
    生成单元测试覆盖率前文提到添加了编译参数-fprofile-arcs、-ftest-coverage已经生成了gcno文件,单元测试运行后也产生了gcda文件。并且我们已经安装好lcov,那么该如何使用lcov来生成覆盖率报告呢?进入到我们生成了*.gcno*.gcda文件的目录收集覆盖率数据(*.gcda)并......
  • 【C++ | const成员】一文了解类的 const数据成员、const成员函数、const对象、mutable
    ......
  • OpenGL3.3_C++_Windows(10)
    最终演示​demo演示Assimp模型渲染模型导入库Assimp:导入很多种不同的模型文件格式,加载至Assimp的通用数据结构(树形)中,不论导入的是什么种类的文件格式,用同一种方式访问我们需要的数据。Assimp库配置:premake5.lua:cmake构建出sln,对于assimpproject构建动态库......
  • OpenGL3.3_C++_Windows(9)
    最终效果demo演示多光源原理:所有投光物分别计算,对当前片段的影响,再+求和,渲染出物体的材质效果每个投光物:根据冯氏光照(环境,漫反射,镜面)分解计算对片段的强度影响,再与当前片段颜色值(单一颜色/纹理颜色)*相乘每个投光物也会对(环境,漫反射,镜面)有不同的影响程度通......
  • C/C++中的extern关键词
    于《C++Primer》的学习中遇到extern关键词的详细解释以下将抛开复杂的解释,仅于简单上手的使用方面进行非专业的介绍。倘若我们有多个文件如头文件,文件1,文件2...,而我们想将一个变量或者一个函数于多个文件中同时使用(如文件2中定义了一个函数或者变量,则可在文件1或其他文件直接调......
  • 【华为OD机试真题】159、星际篮球争霸赛 | 机试真题+思路参考+代码解析(C++、Java、Py
    文章目录一、题目......