前言:
- 这里的多线程主要指算法部署时所涉及的多线程内容,对于其他多线程知识需要自行补充
- 常用组件有thread、mutex、promise、future、condition_variable
- 启动线程,thread,以及join、joinable、detach、类函数启动为线程
- 生产者消费者模式
- 队列溢出的问题,生产太快,消费太慢。如何实现溢出限制
- 生产者如何拿到消费反馈
- RAII+接口模式的生产者消费者封装,以及多batch的体现
1、线程:
本单元是https://blog.csdn.net/zhuangtu1999/article/details/130903594?spm=1001.2014.3001.5501#t1的实例化单元,更加注重在实战中如何运用线程知识
上图是我们之前的一个例子,可以看到如果没有t.join(),那么线程是会崩掉的,因为他的生命周期在main()函数里面,所以当main结束之后他也会析构掉,但线程里面还在执行,所以会出现错误。
那如果要是没有启动线程就join呢?
可以看到也是会出错的。
所以可以发现:当线程启动了,就必须join,但如果线程没有启动,那就一定不能join,那这样肯定是很麻烦的,所以引申出来了下面的joinable概念:
joinable:
if (t1.joinable()) { t1.join(); }
这样的话无论线程有没有启动,退出的时候都可以正常退出。
detach
detach是分离线程,取消管理权,使线程变成野线程。但这个通常不建议使用
如果一个线程一旦detach之后,线程就交给了系统作管理,当程序结束后自动退出
可以看到在延迟了500毫秒之后,worker down这条语句来不及打印,就会直接退出。
参数传递:
在传递引用类型时,传入参数应用ref
类函数:
如果我们正常想搞一个类函数私有线程和方法,那我们首先想到的是可以这样做:
class Infer{
public:
Infer(){
worker_thread = thread(Infer_worker);
}
private:
thread worker_thread;
void Infer_worker(){
}
};
但这样做会报错:
显示他不是一个静态函数,但如果我们想要把这个函数加上static
这样做的确可以成功运行,但每次都要加上sellf很麻烦。
可以通过取地址方式,保留this参数,这样做可以避免static无法使用this->的操作。
2、生产者消费者模式
生产者:
queue<string> qjobs_; void video_capture(){ int pic_id = 0 ; while (true) { /* code */ char name[100]; sprintf(name ,"PIC - %d" , pic_id++); printf("生产了一个新图片:%s\n" , name ); qjobs_.push(name); this_thread::sleep_for(chrono::milliseconds(1000)); } }
消费者:
void infer_worker(){ while (true) { if (!qjobs_.empty()) { /* code */auto pic = qjobs_.front(); qjobs_.pop(); printf("消费掉一个图片:%s \n" , pic.c_str()); this_thread::sleep_for(chrono::milliseconds(1000)); } this_thread::yield(); } }
结果就是这样:
由于生产一个图片需要1ms,消费一张图片需要1ms,所以二者刚好是收支平衡的状态。
但这个设计到了一个共享资源访问的问题,queue不是线程安全的。
这就是设计到了mutex(https://blog.csdn.net/zhuangtu1999/article/details/130917521?spm=1001.2014.3001.5501)
我们设计了一个mutex然后在创建和消费的时候都加上🔓:
mutex lock_;
void video_capture(){
int pic_id = 0 ;
while (true)
{
/* code */
{
lock_guard<mutex> l(lock_);
char name[100];
sprintf(name ,"PIC - %d" , pic_id++);
printf("生产了一个新图片:%s\n" , name );
qjobs_.push(name);
this_thread::sleep_for(chrono::milliseconds(2000));
}
}
}
void infer_worker(){
while (true)
{
if (!qjobs_.empty())
{
{
lock_guard<mutex> l(lock_);
auto pic = qjobs_.front();
qjobs_.pop();
printf("消费掉一个图片:%s \n" , pic.c_str());
}
this_thread::sleep_for(chrono::milliseconds(1000));
}
this_thread::yield();
}
}
这就变成了一个原子操作。
condtion_varible:
如果生产太快,消费太慢,队列就会不断的增长,如果存储的是图片,可能就会造成显存爆炸:
所以设置一个上限就很有必要