C++多线程之间,线程函数启动之后,线程间依赖的启动和唤醒操作
一、原理分析
1. 线程依赖关系
目标检测中,为了加快速度,往往需要多线程。但是线程之间有依赖关系。然后实时控制每个线程的启动和运行。至关重要。本文讲解了,多线程函数中,线程之间有依赖关系,共享数据也有依赖。
线程ABCD。A完成后通知B,B完成业务后,通知C,C完成后,通知D,D完成后,通知A。如此循环下去。A—>B—>C—>D—>A。
我们先启动这四个线程。每个线程两个锁,一个是锁定当前的任务,让下一个任务等待,直到该线程处理完毕,再通知下一个业务线程。 另外一个锁是等待的锁,用条件变量,等上一个业务线程的通知。一旦上一个业务完成后,马上被唤醒。
对于线程B来说,需要等待A完成,等待这个锁解锁(A_B_mutex);此时,自己也要拿住对下一个线程相关联的,C的锁(B_C_mutex)。就有两个锁。主动拥有B–>C的锁,被动锁定A–>B的锁,等待A的条件变量完成,并且被通知唤醒解锁。
二、 实例分析
在我们的业务中。我们有四个内容,分别是发送信号SendCom(); 图像转换ImageConvert(); 目标检测ObjectDetection();NMS和重采样算法NMSResample()。 他们之间的内容,分别有相互共享的数据。且每一步,都需要依赖上一步的数据,以及等待上一步完成。 比如,目标检测线程,需要图像格式转换线程完毕,才能进行图像的检测。NMS重采样线程,需要对目标检测结果(目标检测线程完成),进行综合分析,完成之后,将目标的位置信号发送给SendCom线程,进行采集信号发送。他们之间形成了闭环,需要由某个线程,主动启动,才能打通依赖关系线程的开启。
因此,我们在主线程中,主动建立了一个锁,叫做主函数和NMS重采样之间的锁。通过这个锁,我们从主线程唤醒多线程中的NMSResample()线程,从而开启流水处理的多线程间相互依赖方式。
如下代码中:main_scan_mutex 来开启这个主线程多多线程的唤醒。
2.1 多线程启动
#include <iostream>
#include <time.h>
#include <thread>
#include <condition_variable>
using namespace std;
mutex send_cvt_mutex,cvt_detect_mutex, detect_nms_mutex, nms_send_mutex, main_scan_mutex;
condition_variable send_cvt_cv, cvt_detect_cv, detect_nms_cv, nms_send_cv, main_scan_cv;
bool is_send_done = false;
bool is_cvt_done = false;
bool is_detect_done = false;
bool is_nms_done = false;
int total_scan_time = 0;
void threadSendCom()
{
cout << "1. Send Com Thread!" << endl;
while (total_scan_time < 5)
{
this_thread::sleep_for(chrono::microseconds(2));
unique_lock<mutex> send_cvt_lck(send_cvt_mutex);
unique_lock<mutex> nms_send_lck(nms_send_mutex);
nms_send_cv.wait(nms_send_lck, [] {return is_nms_done; });
cout << " >>>> Is send com: " << total_scan_time << endl;
this_thread::sleep_for(chrono::milliseconds(2));
is_send_done = true;
send_cvt_cv.notify_one();
is_nms_done = false;
}
cout << " Thread 1 end!" << endl;
}
void threadImageConvert()
{
cout << "2. Img convert Thread!" << endl;
while (total_scan_time < 5)
{
this_thread::sleep_for(chrono::microseconds(2));
unique_lock<mutex> cvt_detect_lck(cvt_detect_mutex);
unique_lock<mutex> send_cvt_lck(send_cvt_mutex);
send_cvt_cv.wait(send_cvt_lck, [] {return is_send_done; });
cout << " >>>> Is convert image: " << total_scan_time << endl;
this_thread::sleep_for(chrono::milliseconds(2));
is_cvt_done = true;
cvt_detect_cv.notify_one();
send_cvt_lck.unlock();
is_send_done = false;
}
cout << "Thread 2 end!" << endl;
}
void threadObjectDetection()
{
cout << "3. Object detect Thread!" << endl;
while (total_scan_time < 5)
{
this_thread::sleep_for(chrono::microseconds(2));
unique_lock<mutex> detect_nms_lck(detect_nms_mutex);
unique_lock<mutex> cvt_detect_lck(cvt_detect_mutex);
cvt_detect_cv.wait(cvt_detect_lck, [] {return is_cvt_done; });
cout << " >>>>Is object detect: " << total_scan_time << endl;
this_thread::sleep_for(chrono::milliseconds(2));
is_detect_done = true;
detect_nms_cv.notify_one();
cvt_detect_lck.unlock();
is_cvt_done = false;
}
cout << "Thread 3 end!" << endl;
}
void threadNMSResample()
{
cout << "4. Nms and resample Thread!" << endl;
while (total_scan_time < 5)
{
this_thread::sleep_for(chrono::microseconds(2));
unique_lock<mutex> nms_send_lck(nms_send_mutex);
if (total_scan_time == 0)
{
unique_lock<mutex> main_scan_lck(main_scan_mutex);
cout << "!!! Waiting here for main thread unlock mutex" << endl;
main_scan_cv.wait(main_scan_lck, [] {return is_detect_done; });
}
else
{
unique_lock<mutex> detect_nms_lck(detect_nms_mutex);
detect_nms_cv.wait(detect_nms_lck, [] {return is_detect_done; });
}
cout << " >>>> Is NMS resample:" << total_scan_time +1 << endl;
this_thread::sleep_for(chrono::milliseconds(2));
is_nms_done = true;
nms_send_cv.notify_one();
is_detect_done = false;
total_scan_time += 1;
}
cout << "Thread 4 end!" << endl;
}
int main()
{
thread nms_resample_thread(threadNMSResample);
thread send_com_thread(threadSendCom);
thread img_cvt_thread(threadImageConvert);
thread object_detect_thread(threadObjectDetection);
this_thread::sleep_for(chrono::milliseconds(100));
{
cout << ">>>> Main thread begin!!!!!!" << endl;
unique_lock<mutex> main_scan_lck(main_scan_mutex);
is_detect_done = true;
}
main_scan_cv.notify_one();
nms_resample_thread.join();
object_detect_thread.join();
img_cvt_thread.join();
send_com_thread.join();
cout << " End all" << endl;
}
2.2 多线程模式讲解
(1) 多线程开启与主线程唤醒
下面就描述了,多线程间的依赖关系。他们之间形成了闭环。必须要主线程去主动开启通知。 蓝色的箭头表示。 对于主线程去唤醒,我们用条件变量来通知。用的main_scan_cv来进行通知。
(2)单线程需要2个锁,一个主动加锁,一个等待互斥锁释放。
对单个线程,就需要2个锁。比如,图像转换线程。他的数据,来自于发送的信号,SendCom线程处理的结果,才是需要转换的图像。只有图像格式转换完毕之后,目标检测线程,ObjectDetection才能工作。那么,我们就需要两个锁定。 1:主动拥有 cvt_detect_lck。获得cvt_detect_mutex的所有权。把下一个模块,目标检测目标给阻塞住。 对于当前的图像格式转换业务,我们一直等待 上一个模块通知,我们就用条件变量,send_cvt_cv 来wait。直到上一个模块,发起通知,告诉我,发送已经完成,is_send_done。 2:当is_send_done变为true。我们就不用wait,继续执行下面的业务。所以需要send_cvt_lck。 被动拥有和轮询send_cvt_lck的状态解锁。
(3) 运行结果展示
所以,我们最后的结果就是,每个模块,都运行起来。由主函数唤醒一次。剩下就是多线程之间,互相启动的运行过程。我们运行这个目标检测流程5遍,然后退出。下面就是展示运行结果。
|