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 多线程启动

// MultiThreadSimulation.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <iostream>
#include <time.h>
#include <thread>
#include <condition_variable>

using namespace std;

/* condition lock, and mutex for thread controlling. */
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()
{
    // Resample --> SendCom --> ImgConvert --> ObjectDetection.
    // 建立线程函数
    thread nms_resample_thread(threadNMSResample);
    thread send_com_thread(threadSendCom);
    thread img_cvt_thread(threadImageConvert);
    thread object_detect_thread(threadObjectDetection);

    // 主线程 解锁并 唤醒通知 threadNMSResample 线程,开启循环。
    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遍,然后退出。下面就是展示运行结果。
在这里插入图片描述

如果有用,记得点赞👍加收藏哦。!!!!

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值