部署中多线程常用组件: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;
}
本文介绍了C++中实现生产者消费者模式的方法,包括基本的队列操作、互斥锁和条件变量的使用,以及如何处理队列溢出和生产者获取消费者反馈的情况。通过示例代码展示了如何利用线程安全的数据结构和并发工具来协调生产者和消费者的执行,确保系统稳定和高效。
&spm=1001.2101.3001.5002&articleId=131649640&d=1&t=3&u=2312b921b8844f1baae993cbbeab1cfd)
319

被折叠的 条评论
为什么被折叠?



