C++11多线程笔记四(部署中的生产者消费者模式)

部署中多线程常用组件:thread, join, joinable, detach, mutex, promise, future, condition_variable, 生产者消费者模式

一、模拟部署中的生产者消费者

1. 最简单实现

#include <iostream>
#include <thread>
#include <chrono>
#include <queue>
#include <mutex>
#include <string>
#include <stdio.h>

using namespace std;
queue<string> qjobs_;

void video_capture() {
	int pic_id = 0;
	while (true) {
		char name[100];
		sprintf_s(name, "PIC-%d", pic_id++);
		printf("生成了一个新图片:%s\n", name);
		qjobs_.push(name);
		//读图1s
		this_thread::sleep_for(chrono::milliseconds(1000));
	}
}

void infer_worker() {
	while (true) {
		if (!qjobs_.empty()) {
			auto pic = qjobs_.front();
			qjobs_.pop();
			printf("消耗掉一个图片:%s\n", pic.c_str());
			//推理1s
			this_thread::sleep_for(chrono::milliseconds(1000));
		}
		this_thread::yield();    //交出cpu控制权,进入空闲状态,继续等待下一个时间片
	}
}
int main() {
	thread t0(video_capture);
	thread t1(infer_worker);
	t0.join();
	t1.join();
	return 0;
}

2. queue,stl对象不是thread-safe,需要加锁

#include <iostream>
#include <thread>
#include <chrono>
#include <queue>
#include <mutex>
#include <string>
#include <stdio.h>

using namespace std;
queue<string> qjobs_;
mutex lock_;

void video_capture() {
	int pic_id = 0;
	while (true) {
		{
			lock_guard<mutex> l(lock_);    //作用域在大括号{},生产过程枷锁,超过作用域析构自动解锁
			char name[100];
			sprintf_s(name, "PIC-%d", pic_id++);
			printf("生成了一个新图片:%s\n", name);
			qjobs_.push(name);
		}//自动解锁
		//读图1s
		this_thread::sleep_for(chrono::milliseconds(1000));
	}
}

void infer_worker() {
	while (true) {
		if (!qjobs_.empty()) {
			{
				//lock_guard作用域在大括号{},消费过程枷锁,数据从队列一瞬间加锁,超过作用域析构自动解锁
				//推理等待过程不加锁,不然无法填充
				lock_guard<mutex> l(lock_);
				auto pic = qjobs_.front();
				qjobs_.pop();
				printf("消耗掉一个图片:%s\n", pic.c_str());
			}
			//推理1s
			this_thread::sleep_for(chrono::milliseconds(1000));
		}
		this_thread::yield();    //交出cpu控制权,进入空闲状态,继续等待下一个时间片
	}
}
int main() {
	thread t0(video_capture);
	thread t1(infer_worker);
	t0.join();
	t1.join();
	return 0;
}

二、队列溢出问题:生产太快,消费太慢

1. 生产速度大小消费速度造成队列堆积现象

#include <iostream>
#include <thread>
#include <chrono>
#include <queue>
#include <mutex>
#include <string>
#include <stdio.h>

using namespace std;
queue<string> qjobs_;
mutex lock_;

void video_capture() {
	int pic_id = 0;
	while (true) {
		{
			lock_guard<mutex> l(lock_);    //作用域在大括号{},生产过程枷锁,超过作用域析构自动解锁
			char name[100];
			sprintf_s(name, "PIC-%d", pic_id++);
			printf("生成了一个新图片:%s, qjobs_size = %d\n", name, qjobs_.size());
			qjobs_.push(name);
		}//自动解锁
		//读图1s
		this_thread::sleep_for(chrono::milliseconds(500));
	}
}

void infer_worker() {
	while (true) {
		if (!qjobs_.empty()) {
			{
				//lock_guard作用域在大括号{},消费过程枷锁,数据从队列一瞬间加锁,超过作用域析构自动解锁
				//推理等待过程不加锁,不然无法填充
				lock_guard<mutex> l(lock_);
				auto pic = qjobs_.front();
				qjobs_.pop();
				printf("消耗掉一个图片:%s\n", pic.c_str());
			}
			//推理1s
			this_thread::sleep_for(chrono::milliseconds(1000));
		}
		this_thread::yield();    //交出cpu控制权,进入空闲状态,继续等待下一个时间片
	}
}
int main() {
	thread t0(video_capture);
	thread t1(infer_worker);
	t0.join();
	t1.join();
	return 0;
}

打印结果:
在这里插入图片描述
例子使用的是string,但实际推理中queue中存的是图片,如果图片大小是1280 * 720 * 3,容易显存消耗殆尽,程序崩溃

2. 解决方法:设置生产上限

  • 达到生产上限停止生产,等待消费者推理完成,队列有空间再生产
  • 使用condition_variable,传的参数是unique_lock
  • 流程:判断队列的数量是否超过了limit,如果超过,则一直wait(),且此时解锁,直到消费掉一个,使用notice_one()通知生产者,跳出wait(),同时生产者加锁
#include <iostream>
#include <thread>
#include <chrono>
#include <queue>
#include <mutex>
#include <condition_variable>
#include <string>
#include <stdio.h>

using namespace std;
queue<string> qjobs_;
mutex lock_;
condition_variable cv_;
const int limit_ = 5;

void video_capture() {
	int pic_id = 0;
	while (true) {
		{
			unique_lock<mutex> l(lock_);    //作用域在大括号{},生产过程枷锁,超过作用域析构自动解锁
			char name[100];
			sprintf_s(name, "PIC-%d", pic_id++);
			printf("生成了一个新图片:%s, qjobs_size = %d\n", name, qjobs_.size());
			//if (qjobs_.size() > limit)
			//	wait();
			cv_.wait(l, [&]() {
				//return false:继续等待
				//return true:等待结束,跳出wait
				//进入wait,解锁
				//跳出wait,加锁
				return qjobs_.size() < limit_;
			});
			qjobs_.push(name);
			// 如果队列满了不生产,等待队列有空间再生产
			// 通知的问题,如果即使通知到wait,让他随时可以退出

		}//自动解锁
		//读图1s
		this_thread::sleep_for(chrono::milliseconds(500));
	}
}

void infer_worker() {
	while (true) {
		if (!qjobs_.empty()) {
			{
				//lock_guard作用域在大括号{},消费过程枷锁,数据从队列一瞬间加锁,超过作用域析构自动解锁
				//推理等待过程不加锁,不然无法填充
				lock_guard<mutex> l(lock_);
				auto pic = qjobs_.front();
				qjobs_.pop();
				//消费掉一个,通知wait(),跳出等待
				cv_.notify_one();
				printf("消耗掉一个图片:%s\n", pic.c_str());
			}
			//推理1s
			this_thread::sleep_for(chrono::milliseconds(1000));
		}
		this_thread::yield();    //交出cpu控制权,进入空闲状态,继续等待下一个时间片
	}
}
int main() {
	thread t0(video_capture);
	thread t1(infer_worker);
	t0.join();
	t1.join();
	return 0;
}

打印结果:队列数量不超过limit = 5
在这里插入图片描述

三、生产者拿到消费者的反馈

推理过程中,有很多种模型,比如目标检测、车道线检测、深度估计等,即有很多个消费者,多个消费者异步推理,消费者推理完图片得到的结果要反馈给生产者,进行后处理和画框等

同步模式: detection -> infer, face -> infer, feature -> infer

异步模式: detaction -> push, face -> push, feature -> push 一次进行三个结果的回收,然后处理

#include <iostream>
#include <thread>
#include <chrono>
#include <queue>
#include <mutex>
#include <condition_variable>
#include <future>
#include <memory>
#include <string>
#include <stdio.h>


using namespace std;

struct Job {
	shared_ptr<promise<string>> pro;
	string input;
};
queue<Job> qjobs_;
mutex lock_;
condition_variable cv_;
const int limit_ = 5;

void video_capture() {
	int pic_id = 0;
	while (true) {
		Job job;
		{
			unique_lock<mutex> l(lock_);    //作用域在大括号{},生产过程枷锁,超过作用域析构自动解锁
			char name[100];
			sprintf_s(name, "PIC-%d", pic_id++);
			printf("生成了一个新图片:%s, qjobs_size = %d\n", name, qjobs_.size());
			//if (qjobs_.size() > limit)
			//	wait();
			cv_.wait(l, [&]() {
				//return false:继续等待
				//return true:等待结束,跳出wait
				//进入wait,解锁
				//跳出wait,加锁
				return qjobs_.size() < limit_;
			});
			// 如果队列满了不生产,等待队列有空间再生产
			// 通知的问题,如果即使通知到wait,让他随时可以退出
			job.pro.reset(new promise<string>());
			job.input = name;
			qjobs_.push(job);

		}//自动解锁
		//等待这个job处理完毕,拿到结果
		//.get过后,实现等待,知道promise->set_value执行了,返回值就是result
		auto result = job.pro->get_future().get();
		printf("JOB %s -> %s\n", job.input.c_str(), result.c_str());
		//拿到推理结果,几个推理结果一起画框
		//读图1s
		this_thread::sleep_for(chrono::milliseconds(500));
	}
}

void infer_worker() {
	while (true) {
		if (!qjobs_.empty()) {
			{
				//lock_guard作用域在大括号{},消费过程枷锁,数据从队列一瞬间加锁,超过作用域析构自动解锁
				//推理等待过程不加锁,不然无法填充
				lock_guard<mutex> l(lock_);
				auto pjob = qjobs_.front();
				qjobs_.pop();
				//消费掉一个,通知wait(),跳出等待
				cv_.notify_one();
				printf("消耗掉一个图片:%s\n", pjob.input.c_str());

				auto result = pjob.input + "-----------infer";
				//new_pic返回生产者
				pjob.pro->set_value(result);
			}
			//推理1s
			this_thread::sleep_for(chrono::milliseconds(1000));
		}
		this_thread::yield();    //交出cpu控制权,进入空闲状态,继续等待下一个时间片
	}
}
int main() {
	thread t0(video_capture);
	thread t1(infer_worker);
	t0.join();
	t1.join();
	return 0;
}

打印结果:
在这里插入图片描述

不需要condition_variable

#include <iostream>
#include <thread>
#include <chrono>
#include <queue>
#include <mutex>
#include <future>
#include <memory>
#include <string>
#include <stdio.h>

using namespace std;
struct Job {
	shared_ptr<promise<string>> pro;
	string input;
};
queue<Job> qjobs_;
mutex lock_;
const int limit_ = 5;

void video_capture() {
	int pic_id = 0;
	while (true) {
		Job job;
		{
			lock_guard<mutex> l(lock_);    //作用域在大括号{},生产过程枷锁,超过作用域析构自动解锁
			char name[100];
			sprintf_s(name, "PIC-%d", pic_id++);
			printf("生成了一个新图片:%s, qjobs_size = %d\n", name, qjobs_.size());

			job.pro.reset(new promise<string>());
			job.input = name;
			qjobs_.push(job);

		}//自动解锁
		//等待这个job处理完毕,拿到结果
		//.get过后,实现等待,知道promise->set_value执行了,返回值就是result
		auto result = job.pro->get_future().get();
		printf("JOB %s -> %s\n", job.input.c_str(), result.c_str());
		//拿到推理结果,几个推理结果一起画框
		//读图1s
		this_thread::sleep_for(chrono::milliseconds(500));
	}
}

void infer_worker() {
	while (true) {
		if (!qjobs_.empty()) {
			{
				//lock_guard作用域在大括号{},消费过程枷锁,数据从队列一瞬间加锁,超过作用域析构自动解锁
				//推理等待过程不加锁,不然无法填充
				lock_guard<mutex> l(lock_);
				auto pjob = qjobs_.front();
				qjobs_.pop();
				printf("消耗掉一个图片:%s\n", pjob.input.c_str());

				auto result = pjob.input + "-----------infer";
				//new_pic返回生产者
				pjob.pro->set_value(result);
			}
			//推理1s
			this_thread::sleep_for(chrono::milliseconds(1000));
		}
		this_thread::yield();    //交出cpu控制权,进入空闲状态,继续等待下一个时间片
	}
}
int main() {
	thread t0(video_capture);
	thread t1(infer_worker);
	t0.join();
	t1.join();
	return 0;
}

多个推理模型的模拟(一个生产者多个消费者):

#include <iostream>
#include <thread>
#include <chrono>
#include <queue>
#include <mutex>
#include <future>
#include <memory>
#include <string>
#include <stdio.h>

using namespace std;
struct Job {
	shared_ptr<promise<string>> pro;
	string input;
};
queue<Job> qjobs1_;
queue<Job> qjobs2_;
queue<Job> qjobs3_;
mutex lock_;

// 图像处理函数示例:目标检测
string detect(const string& image) {
	// 模拟目标检测的处理时间
	this_thread::sleep_for(chrono::milliseconds(1000));

	cout << "目标检测完成" << endl;
	return "目标检测结果";
}

// 图像处理函数示例:车道线检测
string laneDetection(const string& image) {
	// 模拟车道线检测的处理时间
	this_thread::sleep_for(chrono::milliseconds(1500));
	cout << "车道线检测完成" << endl;
	return "车道线检测结果";
}

// 图像处理函数示例:深度估计
string depthEstimation(const string& image) {
	// 模拟深度估计的处理时间
	this_thread::sleep_for(chrono::milliseconds(2000));
	cout << "深度估计完成" << endl;
	return "深度估计结果";
}

void video_capture() {
	int pic_id = 0;
	while (true) {
		Job job1;
		Job job2;
		Job job3;
		{
			lock_guard<mutex> l(lock_);    //作用域在大括号{},生产过程枷锁,超过作用域析构自动解锁
			char name[100];
			sprintf_s(name, "PIC-%d", pic_id++);
			printf("生成了一个新图片:%s, qjobs_size = %d\n", name, qjobs1_.size());

			job1.pro.reset(new promise<string>());
			job1.input = name;
			job2.pro.reset(new promise<string>());
			job2.input = name;
			job3.pro.reset(new promise<string>());
			job3.input = name;
			qjobs1_.push(job1);
			qjobs2_.push(job2);
			qjobs3_.push(job3);
		}//自动解锁
		//等待这个job处理完毕,拿到结果
		//.get过后,实现等待,知道promise->set_value执行了,返回值就是result
		auto result1 = job1.pro->get_future().get();
		auto result2 = job2.pro->get_future().get();
		auto result3 = job3.pro->get_future().get();
		printf("JOB %s -> %s\n", job1.input.c_str(), result1.c_str());
		printf("JOB %s -> %s\n", job2.input.c_str(), result2.c_str());
		printf("JOB %s -> %s\n", job3.input.c_str(), result3.c_str());
		//拿到推理结果,几个推理结果一起画框
		//读图1s
		this_thread::sleep_for(chrono::milliseconds(2000));
	}
}

void infer_worker1() {
	while (true) {
		if (!qjobs1_.empty()) {
			Job pjob;
			{
				//lock_guard作用域在大括号{},消费过程枷锁,数据从队列一瞬间加锁,超过作用域析构自动解锁
				//推理等待过程不加锁,不然无法填充
				lock_guard<mutex> l(lock_);
				pjob = qjobs1_.front();
				qjobs1_.pop();
				printf("消耗掉一个图片:%s\n", pjob.input.c_str());
			}
			//目标检测
			string detectionResult = detect(pjob.input);
			auto result = pjob.input + "----------" + detectionResult;
			//new_pic返回生产者
			pjob.pro->set_value(result);
		}
		this_thread::yield();    //交出cpu控制权,进入空闲状态,继续等待下一个时间片
	}
}

void infer_worker2() {
	while (true) {
		if (!qjobs2_.empty()) {
			Job pjob;
			{
				//lock_guard作用域在大括号{},消费过程枷锁,数据从队列一瞬间加锁,超过作用域析构自动解锁
				//推理等待过程不加锁,不然无法填充
				lock_guard<mutex> l(lock_);
				pjob = qjobs2_.front();
				qjobs2_.pop();
				printf("消耗掉一个图片:%s\n", pjob.input.c_str());
			}
			//车道线检测
			string laneDetectionResult = laneDetection(pjob.input);
			auto result = pjob.input + "----------" + laneDetectionResult;
			//new_pic返回生产者
			pjob.pro->set_value(result);
		}
		this_thread::yield();    //交出cpu控制权,进入空闲状态,继续等待下一个时间片
	}
}

void infer_worker3() {
	while (true) {
		if (!qjobs3_.empty()) {
			Job pjob;
			{
				//lock_guard作用域在大括号{},消费过程枷锁,数据从队列一瞬间加锁,超过作用域析构自动解锁
				//推理等待过程不加锁,不然无法填充
				lock_guard<mutex> l(lock_);
				pjob = qjobs3_.front();
				qjobs3_.pop();
				printf("消耗掉一个图片:%s\n", pjob.input.c_str());
			}
			//深度估计
			string depthEstimationResult = depthEstimation(pjob.input);
			auto result = pjob.input + "----------" + depthEstimationResult;
			//new_pic返回生产者
			pjob.pro->set_value(result);
		}
		this_thread::yield();    //交出cpu控制权,进入空闲状态,继续等待下一个时间片
	}
}

int main() {
	thread t0(video_capture);
	thread t1(infer_worker1);
	thread t2(infer_worker2);
	thread t3(infer_worker3);
	t0.join();
	t1.join();
	t2.join();
	t3.join();
	return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值