Between C + + multithreads, after the thread function is started, multithread dependent startup and thread wake-up operations.

Posted by crochk on Sun, 05 Dec 2021 16:20:42 +0100

1, Principle analysis

1. Thread dependency

This article explains that in multithreaded functions, there are dependencies between threads and shared data.

Thread ABCD. A notifies B after completion, B notifies C after completion, C notifies d after completion, and a after completion. This cycle continues. A—>B—>C—>D—>A.

Let's start these four threads first. Each thread has two locks. One is to lock the current task and let the next task wait. Notify the next business thread until the thread has finished processing. Another lock is the waiting lock, condition variable, and the notification of the previous business thread. Once the previous service is completed, it will be awakened immediately.
For thread B, you need to wait for A to complete and wait for the lock to be unlocked (A_B_mutex); At this time, you should also hold the lock of C (B_C_mutex) associated with the next thread. There are two locks.

2, Case analysis

In our business. We have four contents: send signal, SendCom(); Image conversion ImageConvert(); Object detection(); NMS and resampling algorithm NMSResample()
The content between them has shared data. And each step needs to rely on the data of the previous step and wait for the completion of the previous step.

In the main thread, we actively establish a lock called the lock between the main function and resampling. Through this lock, we wake up the NMSResample() thread in the multithread from the main thread, so as to turn on the interdependence between multithreads of pipelining.

In the following code: main_scan_mutex to start the wake-up of the main thread and multiple threads.

2.1 multithreading startup

// MultiThreadSimulation.cpp: this file contains the "main" function. Program execution will begin and end here.
//

#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) //For the first time, wait for the main thread to wake up. This opens the cycle.
        {
            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.
    // Establish thread function
    thread nms_resample_thread(threadNMSResample);
    thread send_com_thread(threadSendCom);
    thread img_cvt_thread(threadImageConvert);
    thread object_detect_thread(threadObjectDetection);

    // The main thread unlocks and wakes up the notification threadNMSResample thread to start the loop.
    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 explanation of multithreading mode

(1) Multithreading startup and main thread wake-up

The following describes the dependencies between multiple threads. A closed loop was formed between them. The main thread must take the initiative to open notifications.

For the main thread to wake up, we use condition variables to notify.

(2) A single thread requires two locks, one actively locks and one waits for the release of the mutex.

For a single thread, two locks are required. For example, image conversion thread. His data comes from the signal sent, and the result processed by the SendCom thread is the image to be converted. Only after the image format conversion is completed can the object detection thread work. Then we need two locks.
1: Active ownership of cvt_detect_lck. Obtain CVT_ detect_ Ownership of mutex. Block the next module, target detection target. For the current image format conversion service, we have been waiting for the notification of the previous module, and we use the conditional variable, send_cvt_cv, wait. Until the previous module, it initiates a notification and tells me that the sending has been completed, is_send_done.
2. When is_send_done becomes true. We don't have to wait and continue to perform the following business. So you need send_cvt_lck.

(3) Display of operation results

So, our final result is that each module runs. Wake up once by the main function. The rest is the running process started by multiple threads.

Topics: C++ Back-end Multithreading Computer Vision