当前位置:网站首页>[QT] subclass qthread to realize multithreading

[QT] subclass qthread to realize multithreading

2020-11-09 08:20:00 Li Chungang

Subclass QThread To implement multithreading , QThread Only run Function is in a new thread , All the other functions are in QThread In the generated thread . The correct way to start a thread is to call QThread::start() To start up , If called directly run Member functions , No new threads will be generated at this time ( reason : You can check the past 《QThread Source analyses 》 article , Get to know run How functions are called ).

One 、 step

  • Subclass  QThread;
  • rewrite run, Put the time-consuming event into this function to execute ;
  • Depending on whether an event loop is needed , If you need to run Call in function QThread::exec() , Turn on the thread's event loop . The role of the event loop can be viewed in the past 《QThread Source analyses 》 In the article 《QThread::run() Source code 》 Read in small sections ;
  • Define signals and slots for subclasses , Because slot functions don't run on New Threads , So we need to call in the constructor.  moveToThread(this). Be careful : Although calling moveToThread(this) You can change the thread dependency of an object , however QThread Most of the member methods are thread control interfaces ,QThread Class is designed to provide the control interface of the thread to the old thread ( establish QThread Object's thread ) Use . So don't use moveToThread() Move the interface to the newly created thread , call moveToThread(this) Seen as a bad implementation .

The next step is through Using threads to implement timers , And in real time UI Displayed on the To illustrate the situation of not using event loop and using event loop .( This example uses QTimer Will be more convenient , Here is to illustrate QThread Use , So thread is used to implement )

Two 、 Do not use event loop instances

InheritQThread.hpp

class InheritQThread:public QThread
{
    Q_OBJECT
public:
    InheritQThread(QObject *parent = Q_NULLPTR):QThread(parent){
        
    }
    
    void StopThread(){
        QMutexLocker lock(&m_lock);
        m_flag = false;
    }
    
protected:
    // Thread execution function 
    void run(){
        qDebug()<<"child thread = "<<QThread::currentThreadId();
        int i=0;
        m_flag = true;
        
        while(1)
        {
            ++i;
            emit ValueChanged(i); // There is no need for an event loop mechanism to send signals 
            QThread::sleep(1);
            
            {
                QMutexLocker lock(&m_lock);
                if( !m_flag )
                    break;
            }
            
        }
    }
    
signals:
    void ValueChanged(int i);
    
public:
    bool m_flag;
    QMutex m_lock;
};

mainwindow.hpp

class MainWindow : public QMainWindow
{
    Q_OBJECT
public:
    explicit MainWindow(QWidget *parent = nullptr) :
        QMainWindow(parent),
        ui(new Ui::MainWindow){
        ui->setupUi(this);
        
        qDebug()<<"GUI thread = "<<QThread::currentThreadId();
        WorkerTh = new InheritQThread(this);
        connect(WorkerTh, &InheritQThread::ValueChanged, this, &MainWindow::setValue);
    }
    
    ~MainWindow(){
        delete ui;
    }
    
public slots:
    void setValue(int i){
        ui->lcdNumber->display(i);
    }
    
private slots:
    void on_startBt_clicked(){
        WorkerTh->start();
    }
    
    void on_stopBt_clicked(){
        WorkerTh->StopThread();
    }
    
    void on_checkBt_clicked(){
        if(WorkerTh->isRunning()){
            ui->label->setText("Running");
        }else{
            ui->label->setText("Finished");
        }
    }
    
private:
    Ui::MainWindow *ui;
    InheritQThread *WorkerTh;
};

When using multithreading , If there is a shared resource usage , We need to pay attention to the problem of resource snatching , For example, the above InheritQThread Class m_flag A variable is a resource used by multiple threads at the same time , The above example uses QMutexLocker+QMutex To protect the use of critical resources , It actually uses RAII technology :(Resource Acquisition Is Initialization), Also known as “ Resource acquisition is initialization ”, yes C++ A management resource of language 、 To avoid leakage .C++ Standards guarantee that in any case , The constructed object will eventually be destroyed , That is, its destructor will eventually be called . To put it simply ,RAII The way to do this is to use an object , Get resources when they are constructed , Control access to resources over the life of an object to keep it valid at all times , Finally, the resources are released when the object is destructed . Specifically QMutexLocker+QMutex The principle of mutex and how to use it , I don't want to talk about it here , There are many excellent articles on the Internet .

effect :

(1) At no point 【start】 When you press the key , Click on 【check thread state】 Button to check thread status , The thread is not open .

(2) Press down 【start】 The effect is as follows , And check the terminal message print information :


Only called QThread::start() after , The child thread is the real start , And only in run() The function is in the child thread .

(3) Let's try again and click 【stop】 Button , Then check the state of the thread :

Click on 【stop】 The button makes m_flag = false, here run Function can also jump out of the loop , And it stops the thread , After that, we can't use the thread again , Maybe some people say , I'll do it again start No, that's good ? Once again, start It's not the thread you just used , This is a start Is a new thread . This subclass  QThread , We don't use threads to implement events , It's that simple .

3、 ... and 、 Use the event loop instance

run Function while perhaps for After loop execution , If you want to keep threads running , Continue to use later , What should I do ?
You can start an event loop for child threads , And use the signal slot to continue to use the child thread . Be careful : Be sure to use the signal slot way , Otherwise, the function is still creating QThread Object thread execution .

  • stay run Function to add QThread::exec() To start the event loop .( Be careful : Without exiting the event loop ,QThread::exec() None of the following statements can be executed , After exiting, the program will continue to execute the following statements );
  • by QThread Subclasses define signals and slots ;
  • stay QThread In the subclass constructor, it is called  moveToThread(this)( Be careful : The constructor can be implemented within the child thread , This method is not recommended , Better methods will be introduced in later articles ).

Then the above example , stay InheritQThread Class constructor to add and call moveToThread(this); stay run Function to add exec(); And define the slot function :

/************** stay InheritQThread Constructors add moveToThread(this)**********/
InheritQThread(QObject *parent = Q_NULLPTR):QThread(parent){
        moveToThread(this); 
    }

/************** stay InheritQThread::run Function add exec()***************/
void run(){
    qDebug()<<"child thread = "<<QThread::currentThreadId();

    int i=0;
    m_flag = true;

    while(1)
    {
        ++i;

        emit ValueChanged(i);
        QThread::sleep(1);

        {
            QMutexLocker lock(&m_lock);
            if( !m_flag )
                break;
        }
    }
    
    exec(); // Turn on the event loop 
    }

/************ stay InheritQThread Class QdebugSlot() Slot function ***************/
public slots:
    void QdebugSlot(){
        qDebug()<<"QdebugSlot function is in thread:"<<QThread::currentThreadId();
    }

stay MainWindow Class QdebugSignal The signal ; In the constructor QdebugSignal Signals and InheritQThread::QdebugSlot Slot function to bind ; Add a send QdebugSignal The signal button :

/********** stay MainWindow Bound slot in constructor ******************/
explicit MainWindow(QWidget *parent = nullptr) :
    QMainWindow(parent),
    ui(new Ui::MainWindow){

    qDebug()<<"GUI thread = "<<QThread::currentThreadId();

    ui->setupUi(this);
    WorkerTh = new InheritQThread(this);
    connect(WorkerTh, &InheritQThread::ValueChanged, this, &MainWindow::setValue);
    connect(this, &MainWindow::QdebugSignal, WorkerTh, &InheritQThread::QdebugSlot); // Binding slots 
}

/********MainWindow Class to add a signal QdebugSignal Slot and button event slot functions **********/
signals:
    void QdebugSignal(); // add to QdebugSignal The signal 
private slots:
    // Button event slot function 
    void on_SendQdebugSignalBt_clicked()
    {
        emit QdebugSignal();
    }

The program that implements the event loop has been modified , Let's see the effect :

(1) Why do the following warnings appear when running ?

QObject::moveToThread: Cannot move objects with a parent

We see MainWindow Class is defined as InheritQThread Class object :WorkerTh = new InheritQThread(this). If needed moveToThread() To change the attachment of objects , It cannot be created with a parent class . Change the sentence to :WorkerTh = new InheritQThread() that will do .

(2) After the modification is completed , Click on 【start】 Start thread , And then click 【stop】 The button pops out run Function while loop , Finally, click 【check thread state】 Button to check the state of the thread , What kind of situation would it be ?

As can be seen from the picture above , The thread is still running , This is because run Called in the function exec(), At this point, the thread is in an event loop .

(3) Next, click on 【Send QdebugSignal】 Button to send QdebugSignal The signal .

From the terminal print information that ,InheritQThread::QdebugSlot Slot functions are executed in child threads .

Four 、 Subclass QThread Signals and slots for threads

It can be seen from the figure above , The event loop is an endless loop , Before the end of the event cycle ,exec() The statement after the function cannot be executed . Only the thread where the slot function is located has opened the event loop , It can only be called after the corresponding signal is transmitted . Whether the event loop is on or not , After the signal is sent, it will directly enter the event queue of the thread attached to the slot function , However , Only when the event loop is turned on , The corresponding slot function will be called in the thread . The following is to verify through several cases :

(1) Code and 《 3、 ... and 、 Using event loops 》 The code for the section is the same , Then do the following operations : Click on 【start】 Button -> Click again 【Send QdebugSignal】 Button , Will the slot function be executed at this time ?


In this case, no matter how many times you click to send QdebugSignal The signal ,InheritQThread::QdebugSlot None of the slot functions are executed . Because the current thread is still in while Cycle of , If you need to implement the slot function, execute it in the current thread , Then the current thread should be in an event loop state , That is to say, it is being implemented exec() function . So if you need to InheritQThread::QdebugSlot Slot function execution , You need to click 【stop】 Button to exit while loop , Let the thread enter the event loop .

(2) stay 《 3、 ... and 、 Using event loops 》 Section of the code based on , hold InheritQThread::run Function delete , Then do the following operations : Click on 【start】 Start thread -> Click on 【stop】 The button pops out run Function while Loop into event loop -> Click on 【Send QdebugSignal】 Button to send QdebugSignal The signal , What will happen ?

The result will be the same as in the first case above , Although the signal is already on the event queue of the child thread , But because the child thread has no event loop , So the slot function will never be executed .

(3) on top 《 3、 ... and 、 Using event loops 》 Section of the code based on , take InheritQThread In the constructor moveToThread(this) Remove . Do the following : Click on 【start】 Start thread -> Click on 【stop】 The button pops out run Function while Loop into event loop -> Click on 【Send QdebugSignal】 Button to send QdebugSignal The signal , What will happen ?

As can be seen from the figure above InheritQThread::QdebugSlot The slot function is actually in GUI In the main thread . because InheritQThread Object we are in the main thread new Coming out , If not used moveToThread(this) To change the attachment of objects , that InheritQThread The object belongs to GUI The main thread , according to connect Execution rules of signal slot , Finally, the slot function is executed in the thread on which the object depends . The signal is bound to the slot connect The details of the function will be later 《 Cross thread slots 》 The article introduces it separately .

5、 ... and 、 How to exit a thread correctly and release resources

InheritQThread Class code does not change , Just like the code above :

#ifndef INHERITQTHREAD_H
#define INHERITQTHREAD_H
#include <QThread>
#include <QMutex>
#include <QMutexLocker>
#include <QDebug>

class InheritQThread:public QThread
{
    Q_OBJECT

public:
    InheritQThread(QObject *parent = Q_NULLPTR):QThread(parent){
        moveToThread(this);
    }

    void StopThread(){
        QMutexLocker lock(&m_lock);
        m_flag = false;
    }

protected:
    // Thread execution function 
    void run(){
        qDebug()<<"child thread = "<<QThread::currentThreadId();

        int i=0;
        m_flag = true;

        while(1)
        {
            ++i;

            emit ValueChanged(i);
            QThread::sleep(1);

            {
                QMutexLocker lock(&m_lock);
                if( !m_flag )
                    break;
            }
        }

        exec();
    }

signals:
    void ValueChanged(int i);

public slots:
    void QdebugSlot(){
        qDebug()<<"QdebugSlot function is in thread:"<<QThread::currentThreadId();
    }

public:
    bool m_flag;
    QMutex m_lock;
};

#endif // INHERITQTHREAD_H

MainWindow Class add ExitBt、TerminateBt Two buttons , Used to invoke WorkerTh->exit(0)、WorkerTh->terminate() Exit thread function . From the past 《QThread Source analyses 》 In the article 《QThread::quit()、QThread::exit()、QThread::terminate() Source code 》 Section to know the call quit and exit It's the same , So we only add ExitBt Button :

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include "ui_mainwindow.h"
#include "InheritQThread.h"
#include <QThread>
#include <QDebug>

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = nullptr) :
        QMainWindow(parent),
        ui(new Ui::MainWindow){

        qDebug()<<"GUI thread = "<<QThread::currentThreadId();

        ui->setupUi(this);
        WorkerTh = new InheritQThread();
        connect(WorkerTh, &InheritQThread::ValueChanged, this, &MainWindow::setValue);

        connect(this, &MainWindow::QdebugSignal, WorkerTh, &InheritQThread::QdebugSlot);
    }

    ~MainWindow(){
        delete ui;
    }

signals:
    void QdebugSignal();

public slots:
    void setValue(int i){
        ui->lcdNumber->display(i);
    }

private slots:
    void on_startBt_clicked(){
        WorkerTh->start();
    }

    void on_stopBt_clicked(){
        WorkerTh->StopThread();
    }

    void on_checkBt_clicked(){
        if(WorkerTh->isRunning()){
            ui->label->setText("Running");
        }else{
            ui->label->setText("Finished");
        }
    }

    void on_SendQdebugSignalBt_clicked(){
        emit QdebugSignal();
    }

    void on_ExitBt_clicked(){
        WorkerTh->exit(0);
    }

    void on_TerminateBt_clicked(){
        WorkerTh->terminate();
    }

private:
    Ui::MainWindow *ui;
    InheritQThread *WorkerTh;
};

#endif // MAINWINDOW_H

Run the above routine , Click on 【start】 Start thread button , Then click directly 【exit(0)】 perhaps 【terminate()】, Does this exit the thread directly ?
Click on 【exit(0)】 Button ( Come on )

Click on 【terminate()】 Button ( Just a little bit )

From the above situation, we can see that after the thread of the above routine is started , No matter how you click 【start】 Button , No thread will exit , Click on 【terminate()】 When the button is pressed, the current thread will be immediately exited . From the past 《QThread Source analyses 》 In the article 《QThread::quit()、QThread::exit()、QThread::terminate() Source code 》 We can see from the section that , If you use QThread::quit()、QThread::exit() To exit the thread , The thread must be in the state of the event loop ( That is to say, it is being implemented exec()), The thread will exit . and QThread::terminate() No matter what state the thread is in, the thread is forced to exit , But there's a lot of instability in this function , It is not recommended to use . Let's take a look at how to exit a thread correctly .

(1) How to exit a thread correctly ?

  • If there is no event loop in the thread , So just use a flag variable to jump out of run Functional while loop , This will exit the thread normally .
  • If there is an event loop in the thread , Then you need to call QThread::quit() perhaps QThread::exit() To end the loop of events . Like the routine I just mentioned , It's not just while loop , After the loop, there is exec(), In this case, you need to let the thread jump out first while loop , Then call QThread::quit() perhaps QThread::exit() To end the loop of events . as follows :

Be careful : Try not to use QThread::terminate() To end the thread , There are a lot of uncertainties in this function .

(2) How to release thread resources correctly ?

Exiting a thread does not mean that the thread's resources are released , Exiting a thread just stops the thread , that QThread Class or QThread How should the resources of derived classes be released ? direct delete QThread Class or derived class ? Of course not , Never do it by hand delete Thread pointer , Manual delete Unexpected accidents will happen . In theory, all QObject It's not supposed to be manual delete, If there is no multithreading , Manual delete There may not be a problem , But in the case of multithreading delete It's very easy to have problems , That's because it's possible that the object you want to delete is in Qt There's still a queue in the event loop , But you've deleted it outside , So the program crashes . Thread resource release can be divided into two situations , One is to create QThread When deriving a class , Added parent object , For example, in MainWindow Class WorkerTh = new InheritQThread(this) Let the main form act as InheritQThread The parent of the object ; The other is not to set any parent class , For example, in MainWindow Class WorkerTh = new InheritQThread().

  • 1、 establish QThread Derived class , There are cases where the parent class is set :

This situation ,QThread The resources of the derived class are taken over by the parent class , When the parent object is destroyed ,QThread Derived class objects are also parented delete fall , We don't need to show delete Destroy resources . But the child thread is not finished yet , The main thread is destroy It fell off (WorkerTh The parent class of is the main thread window , If the main thread window does not wait for the child thread to finish destroy Words , It's easy to handle WorkerTh also delete And then it'll burst ). Be careful : You can't use it in this case moveToThread(this) Change the attachment of objects . So we should put the top MainWindow The constructor of the class is changed to the following :

~MainWindow(){
    WorkerTh->StopThread();// Let the thread exit first while loop 
    WorkerTh->exit();// Exit thread event loop 
    WorkerTh->wait();// Suspends the current thread , wait for WorkerTh Child thread end 
    delete ui;
}
  • 2、 establish QThread Derived class , If no parent is set :

In other words, no parent class takes over resources , Not directly delete QThread Pointer to the derived class object , however QObject There are void QObject::deleteLater () [slot] This slot , This slot is very useful , It is often used later for safe thread resource destruction . We look at the past 《QThread Source analyses 》 In the article 《QThreadPrivate::start() Source code 》 After the thread finishes, it will issue QThread::finished() The signal of , We combine this signal with deleteLater Slot binding , After the thread is finished, it is called deleteLater To destroy the allocated memory .
stay MainWindow Class constructor , Add the following code :

connect(WorkerTh, &QThread::finished, WorkerTh, &QObject::deleteLater) 

~MainWindow() A destructor can put wait() The function takes out , Because the resources of this thread are no longer taken over by the main window . When we start the thread , Then exit the main window or just click 【stop】+【exit()】 Button time , The following warning will appear :

QThread::wait: Thread tried to wait on itself
QThread: Destroyed while thread is still running

In order to enable the child thread to respond to signals and execute slot functions in the child thread , We are InheritQThread Class constructors add moveToThread(this) , This method is highly recommended by the government . So now we have a problem with this method , We put moveToThread(this) Delete , The program can end and release resources normally . If you want the child thread to be able to respond to signals and execute slot functions in the child thread , How should this be done ? In the next issue, we will introduce an officially recommended 《 Subclass QObject+moveToThread》 Methods .

6、 ... and 、 Summary

  • QThread Only run Function is in a new thread ;
  • If you have to implement the scenario of in-process execution slots , Then we need to QThread Called in the derived class constructor moveToThread(this), And in run Function QThread::exec() Turn on the event loop ;( The use of moveToThread(this), The next issue will introduce a safe and reliable method )
  • If you need to use an event loop , Need to be in run Call in function QThread::exec();
  • Try not to use terminate() To end the thread , have access to bool Flag bit exit or call when thread is in an event loop QThread::quit、QThread::exit To exit the thread ;
  • Make good use of QObject::deleteLater For memory management ;
  • stay QThread perform start After the function ,run The function is not finished yet , Again start, Nothing will happen ;
  • Subclass QThread The multithreading method is suitable for long time-consuming operations in the background 、 A single task 、 Scenarios where slots do not need to be executed within a thread .

版权声明
本文为[Li Chungang]所创,转载请带上原文链接,感谢