[C++] Qt-Thread concurrency and thread synchronization

Thread concurrency

We create another console file named main2.cpp, and then create three threads in this file. In these three threads, a global variable is incremented at the same time, and the global variable is output at the end.

int main(int argc, char *argv[])
{
    
    
    QCoreApplication a(argc, argv);

    //线程句柄
    HANDLE handle1 = ::CreateThread(nullptr/*默认的安全属性*/,
                   0/*windows 默认1M*/,
                   &ThreadProc/*线程函数的地址*/,
                   nullptr/*线程函数的参数*/,
                   0/*0:立即运行 ,SUS_PEND 挂起*/,
                   nullptr/*线程ID*/);

    HANDLE handle2 = ::CreateThread(nullptr/*默认的安全属性*/,
                   0/*windows 默认1M*/,
                   &ThreadProc/*线程函数的地址*/,
                   nullptr/*线程函数的参数*/,
                   0/*0:立即运行 ,SUS_PEND 挂起*/,
                   nullptr/*线程ID*/);

    HANDLE handle3 = ::CreateThread(nullptr/*默认的安全属性*/,
                   0/*windows 默认1M*/,
                   &ThreadProc/*线程函数的地址*/,
                   nullptr/*线程函数的参数*/,
                   0/*0:立即运行 ,SUS_PEND 挂起*/,
                   nullptr/*线程ID*/);

    if(WAIT_OBJECT_0 == WaitForSingleObject(handle1,INFINITE)){
    
    
        if(handle1){
    
    
            ::CloseHandle(handle1);
            handle1 = nullptr;
        }
    }

    if(WAIT_OBJECT_0 == WaitForSingleObject(handle2,INFINITE)){
    
    
        if(handle2){
    
    
            ::CloseHandle(handle2);
            handle2 = nullptr;
        }
    }

    if(WAIT_OBJECT_0 == WaitForSingleObject(handle3,INFINITE)){
    
    
        if(handle3){
    
    
            ::CloseHandle(handle3);
            handle3 = nullptr;
        }
    }
    qDebug()<<"N============== "<<N;
    return a.exec();
}

Thread functions and global variables

int N = 0;
//线程函数
DWORD WINAPI ThreadProc (LPVOID lpThreadParameter){
    
    

    for(int i=0;i<100;i++){
    
    
        N++;
        qDebug()<<"N= "<<N;
        Sleep(50);
    }
    return 0;
}

After many tests, we found that the final result was not necessarily the 300 we expected.

[The external link image transfer failed. The source site may have an anti-leeching mechanism. It is recommended to save the image and upload it directly (img-7ghGnBqj-1689425356251) (C++.assets/image-20230706045635228.png)]

What arises here is the concurrency problem:

Multiple threads operating the same resource (memory space, file handle, network handle) at the same time may lead to inconsistent results. The prerequisite for this to happen must be that resources are shared among multiple threads.

[The external link image transfer failed. The source site may have an anti-leeching mechanism. It is recommended to save the image and upload it directly (img-IH7S7mWf-1689425356252) (C++.assets/image-20230706045839262.png)]

So since problems arise, we will also have corresponding solutions, which is thread synchronization that we will talk about next.

Thread synchronization

Thread synchronization is to coordinate the order of thread execution to avoid concurrency problems caused by multiple threads operating the same resource at the same time, so that the results of multiple executions are consistent.

Common thread synchronization methods: atomic access, critical section, event, mutex, condition variable, semaphore.

There are many solutions to the concurrency problems mentioned above, focusing on learning locks.

Atomic access (InterLocked)

At the same time, only one thread is allowed to access one variable. Note: It only maintains atomic increment and decrement operations for a variable, and is not applicable to a code segment.

DWORD WINAPI ThreadProc (LPVOID lpThreadParameter){
    
    
    for(int i=0;i<100;i++){
    
    
        //N++;
        ::InterlockedIncrement((long*)&N);  //源自方式 递增 ++
        //N--;
        //::InterlockedDecrement((long*)&N);
        qDebug()<<"N= "<<N;
        Sleep(50);
    }
    return 0;
}

In this way we will get the ideal value every time we run

[The external link image transfer failed. The source site may have an anti-leeching mechanism. It is recommended to save the image and upload it directly (img-Or8v2Qgy-1689425356252) (C++.assets/image-20230706050549533.png)]

Critical Section (Critical_Section, also called critical section)

Review the problems that arise with singletons

We still remember that when we were learning the singleton pattern in the design pattern, we mentioned a problem, that is, under multi-threading, multiple objects may be created.

Let's try to make this problem visible

Singleton:

class CSingleton {
    
    

    CSingleton(): m_a(0){
    
    }
    CSingleton(const CSingleton&) = delete;  //弃用 拷贝构造函数
    ~CSingleton(){
    
    }
    static CSingleton* m_psin;

    static class Destory {
    
    
    public:
        ~Destory() {
    
    
            if (m_psin) {
    
    
                delete m_psin;
            }
            m_psin = nullptr;
        }
    } m_destory;

public:
    //问题:在多线程下 可能会创建出来多个对象
    static CSingleton* GetSingleton() {
    
    
        //1.加锁
        if (!m_psin)
            m_psin = new CSingleton;
        //2.解锁
        return m_psin;
    }
    static void DestorySingleton(CSingleton*& psin) {
    
    
        if (m_psin) {
    
    
            delete m_psin;
        }
        psin = m_psin = nullptr;
    }

    int m_a;
};
CSingleton* CSingleton::m_psin = nullptr;

CSingleton::Destory CSingleton::m_destory;//类外定义

main function

int main(int argc, char *argv[])
{
    
    
    QCoreApplication a(argc, argv);
    for(int i=0;i<30;i++){
    
    
        CreateThread(nullptr/*默认的安全属性*/,
                           0/*windows 默认1M*/,
                           &ThreadProc/*线程函数的地址*/,
                           nullptr/*线程函数的参数*/,
                           0/*0:立即运行 ,SUS_PEND 挂起*/,
                           nullptr/*线程ID*/);
    }
    return a.exec();
}

Thread function

DWORD WINAPI ThreadProc (LPVOID lpThreadParameter){
    
    
    Sleep(100);
    CSingleton* pSin = CSingleton::GetSingleton();
    qDebug()<<pSin;

    return 0;
}

After running, you can find by looking at the object address that multiple objects may actually be created.

[The external link image transfer failed. The source site may have an anti-leeching mechanism. It is recommended to save the image and upload it directly (img-GdKfrcet-1689425356252) (C++.assets/image-20230706052920751.png)]

Then this problem cannot be solved with atomic access, we need to use key sections

Basic use of key sections

Structure: CRITICAL_SECTION and four functions (initialization, entry, leave, delete)

We can use this to solve problems that arise in singletons

CRITICAL_SECTION m_cs;  //关键段的变量
    static CSingleton* GetSingleton() {
    
    
        //1.加锁
        if (!m_psin){
    
    
            ::EnterCriticalSection(&m_cs);
            if (!m_psin)
            m_psin = new CSingleton;
            //2.解锁
            ::LeaveCriticalSection(&m_cs);
        }
        return m_psin;
    }

Initialization and deletion in the main function. Note that it is best to add a delay before deletion.

    ::InitializeCriticalSection(&m_cs);  //初始化关键段

	......
        
    Sleep(3000);
    ::DeleteCriticalSection(&m_cs);  //删除关键段

In this way, only one object will be created during operation.

[The external link image transfer failed. The source site may have an anti-leeching mechanism. It is recommended to save the image and upload it directly (img-4MyuC6S9-1689425356253) (C++.assets/image-20230706055339117.png)]

Operating principle:

[The external link image transfer failed. The source site may have an anti-leeching mechanism. It is recommended to save the image and upload it directly (img-XdASuiAx-1689425356253) (C++.assets/image-20230706055416621.png)]

Encapsulate key sections

Encapsulate the created object and four structures into one class

class MyLock{
    
    
    CRITICAL_SECTION m_cs;  //关键段的变量
public:
    MyLock(){
    
    
        ::InitializeCriticalSection(&m_cs);  //初始化关键段
    }
    ~MyLock(){
    
    
        ::DeleteCriticalSection(&m_cs);  //删除关键段
    }
    void Lock(){
    
    
        ::EnterCriticalSection(&m_cs);
    }
    void UnLock(){
    
    
        ::LeaveCriticalSection(&m_cs);
    }
};

When using it, you can directly create a class object and call the function.

static CSingleton* GetSingleton() {
    
    

    static MyLock lock;
    //1.加锁
    if (!m_psin){
    
    
        lock.Lock();
        if (!m_psin)
            m_psin = new CSingleton;
        //2.解锁
        lock.UnLock();
    }
    return m_psin;
}

Multithreading under Qt

Multithreading and progress bar

We create a project with a design interface, and put a progress bar and four button components in the design interface. The functions of the components are start, pause, resume and exit respectively.

[The external link image transfer failed. The source site may have an anti-leeching mechanism. It is recommended to save the image and upload it directly (img-kZA18Ere-1689425356254) (C++.assets/image-20230707050229997.png)]

And transfer these buttons directly to the clicked slot function

private slots:
    void on_pb_start_clicked();

    void on_pb_pause_clicked();

    void on_pb_resume_clicked();

    void on_pb_quit_clicked();
void MainWindow::on_pb_start_clicked()
{
    
    

}

void MainWindow::on_pb_pause_clicked()
{
    
    

}

void MainWindow::on_pb_resume_clicked()
{
    
    

}

void MainWindow::on_pb_quit_clicked()
{
    
    

}

Implement thread creation in the started slot function

void MainWindow::on_pb_start_clicked()
{
    
    

    if(!m_handle){
    
    
        m_isQuit = false;
        m_handle = CreateThread(nullptr/*默认的安全属性*/,
                           0/*windows 默认1M*/,
                           &ThreadProc/*线程函数的地址*/,
                           (void*)this/*线程函数的参数*/,
                           0/*0:立即运行 ,SUS_PEND 挂起*/,
                           nullptr/*线程ID*/);
    }

}

Thread function, set the value of the progress bar in the thread function

DWORD WINAPI ThreadProc (LPVOID lpThreadParameter){
    
    

    MainWindow* pMain = (MainWindow*)lpThreadParameter;
    int n = 0;

    while(!pMain->m_isQuit){
    
    
        pMain->GetUI()->progressBar->setValue(n);  //设置进度条的值

        n = ++n%101;
        Sleep(200);
    }
    return 0;
}

Set the public ui interface, exit flag and thread handle in the header file

public:
    Ui::MainWindow * GetUI(){
    
    return ui;}

    bool m_isQuit;  //是否退出
    HANDLE m_handle;

Initialize in constructor

[The external link image transfer failed. The source site may have an anti-leeching mechanism. It is recommended to save the image and upload it directly (img-4A6jruyn-1689425356254) (C++.assets/image-20230707050654263.png)]

At this point, we first click the start button to test and find that the program will crash.

[The external link image transfer failed. The source site may have an anti-leeching mechanism. It is recommended to save the image and upload it directly (img-I3Akl0z2-1689425356254) (C++.assets/image-20230707051136480.png)]

The reason for the error is: when the value of the current component is set in another thread, sendEvent is used to send the event, and an error is reported across threads.

The solution to this situation is to manually set the signals and slots to connect

So we first create a custom slot function and signal

private slots:
    void slots_setValue(int);

signals:
    void signals_sendValue(int);

In this way, we emit the signal in the thread function, then receive the signal in the slot function and set the value of the progress bar

[The external link image transfer failed. The source site may have an anti-leeching mechanism. It is recommended to save the image and upload it directly (img-dGmmFxFW-1689425356255) (C++.assets/image-20230707053100143.png)]

slot function

void MainWindow::slots_setValue(int n){
    
    
    ui->progressBar->setValue(n);  //设置进度条的值
}

Then connect the signals and slots

    connect(this,SIGNAL(signals_sendValue(int)),this,SLOT(slots_setValue(int)),Qt::QueuedConnection);

Note that this function has a fifth parameter, which is used to set the connection type. As long as the error we reported before is because the default connection type does not match, we can choose AutoConnection or QueuedConnection.

This way the program can run normally

[The external link image transfer failed. The source site may have an anti-leeching mechanism. It is recommended to save the image and upload it directly (img-SjiGfc1R-1689425356255) (C++.assets/image-20230707053351443.png)]

Then we will implement the following exit slot function

void MainWindow::on_pb_quit_clicked()
{
    
    
    m_isQuit = true;  //通知线程退出

    if( WAIT_OBJECT_0 == WaitForSingleObject(m_handle,3000)){
    
    
        qDebug()<<"子线程已经退出";
        if(m_handle){
    
    
            CloseHandle(m_handle);
            m_handle = nullptr;
        }
    }
}

Pausing and resuming means suspending and connecting threads

pause

void MainWindow::on_pb_pause_clicked()
{
    
    
    ::SuspendThread(m_handle);
    qDebug()<<"子线程已经挂起";
}

recover

void MainWindow::on_pb_resume_clicked()
{
    
    
    ::ResumeThread(m_handle);
    qDebug()<<"子线程已经恢复";

}

It can run normally after passing the test

Qt-QThread

Qt encapsulates the class for creating threads: QThread. Generally, you manually add a class to inherit QThread, so that you can not only inherit the functions of QThread, but also expand your own functions.

Add new file

[The external link image transfer failed. The source site may have an anti-leeching mechanism. It is recommended to save the image and upload it directly (img-fzZ3zazI-1689425356255) (C++.assets/image-20230711205938295.png)]

[The external link image transfer failed. The source site may have an anti-leeching mechanism. It is recommended to save the image and upload it directly (img-EsNZMdwJ-1689425356255)(C++.assets/image-20230711210119334.png)]

Qt thread + critical segment: Why use the critical segment? The original one just relies on a pause flag. While is always running in vain, it also consumes a little CPU. A better result is to let it wait until it recovers.

So we will create a new thread to block the current thread

head File

#ifndef MYTHREAD_H
#define MYTHREAD_H

#include <QThread>
#include <windows.h>

class MyThread : public QThread
{
    
    
    Q_OBJECT
public:
    explicit MyThread(QObject *parent = 0);
    ~MyThread();

signals:
    void signals_sendThreadValue(int);  //声明信号

public slots:

public:
    virtual void run();

    bool m_isQuit;  //标识当前线程,是否退出
    bool m_isPause;  //是否暂停

    CRITICAL_SECTION m_cs;
};

#endif // MYTHREAD_H

Source File

#include "mythread.h"
#include <QDebug>

MyThread::MyThread(QObject *parent) : QThread(parent),m_isQuit(false),m_isPause(false)
{
    
    
    ::InitializeCriticalSection(&m_cs);
}
MyThread::~MyThread(){
    
    
    ::DeleteCriticalSection(&m_cs);

}

void MyThread::run(){
    
    
    int n = 0;
    while(!m_isQuit){
    
    

        if(m_isPause){
    
    
//            qDebug()<<"暂停了";
//            continue;
            qDebug()<<"暂停了";
            ::EnterCriticalSection(&m_cs);
            qDebug()<<"进入关键段";
            ::LeaveCriticalSection(&m_cs);
            qDebug()<<"离开关键段";

        }

        //发射一个信号
        emit signals_sendThreadValue(n);

        n = ++n%101;
        Sleep(100);
    }
    qDebug()<<"子线程即将退出";
}

Signal and slot connections and implementing thread functionality

    connect(&this->m_thread,SIGNAL(signals_sendThreadValue(int)),this,SLOT(slots_setValue(int)),Qt::QueuedConnection);

Start thread

void MainWindow::on_pb_start_clicked()
{
    
    
    m_thread.m_isQuit = false;
    m_thread.start();  //启动线程
}

Note: To start a thread, you need to call the system function start() instead of calling run() directly.

Pause thread

void MainWindow::on_pb_pause_clicked()
{
    
    
    ::EnterCriticalSection(&m_thread.m_cs);
    m_thread.m_isPause = true;
}

resume thread

void MainWindow::on_pb_resume_clicked()
{
    
    
    m_thread.m_isPause = false;
    ::LeaveCriticalSection(&m_thread.m_cs);
}

Exit thread

void MainWindow::on_pb_quit_clicked()
{
    
    
    m_thread.m_isQuit = true;
}

There is nothing wrong after running the test

[The external link image transfer failed. The source site may have an anti-leeching mechanism. It is recommended to save the image and upload it directly (img-U6mPdhBq-1689425356255) (C++.assets/image-20230711214629735.png)]

At this point, the thread-related knowledge is over.

Guess you like

Origin blog.csdn.net/jia_03/article/details/131743941