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

[QT] subclass QObject + movetothread to realize multithreading

2020-11-09 19:33:25 Li Chungang

Past links :

From the past 《QThread Source analyses 》 You know , stay Qt4.4 Before ,run It's a pure virtual function , Must subclass QThread To achieve run function . And from Qt4.4 Start ,QThread Abstract classes are no longer supported ,run Default call QThread::exec() , There is no need to subclass QThread, Just subclass one QObject, adopt QObject::moveToThread take QObject The derived class can be moved to the thread . This is the official recommendation , And it's flexible to use 、 Simple 、 Safe and reliable . If the thread is going to use an event loop , Using inheritance QObject The multithreading method is undoubtedly a better choice .
This issue is mainly about , Subclass QObject+moveToThread How to use multithreading and some attention problems , There are many details of the problem, actually and in the past 《 Subclass QThread Implement multithreading 》 The article is the same , I don't want to say more here , Don't understand can go to the past 《 Subclass QThread Implement multithreading 》 The article looks for the answer .

One 、 step

  • Write an inheritance QObject Class , Encapsulate complex and time-consuming logic into slot functions , As a thread entry point , There can be more than one entrance ;
  • Create in old thread QObject Derived class objects and QThread object , It's best to use heap allocation to create (new), And it's better not to parent these two objects , It is convenient for resource management of later program ;
  • hold obj adopt moveToThread Method to the new thread , here obj There can't be any parent ;
  • Put the thread of finished Signals and obj object 、QThread Object's QObject::deleteLater Slot connection , This signal slot must be connected to , Otherwise, there will be a memory leak ; If QObject Derived classes and QThread Class pointers need to be reused , Then you need to deal with those sent out immediately before the object is destroyed QObject::destroyed The signal , Set the two pointers to nullptr, Avoid wild pointers ;
  • Connect other signals with QObject Derived class slot join , Used to trigger the thread to execute the task in the slot function ;
  • After the initialization, it is called QThread::start() To start a thread , By default, the event loop is turned on ;
  • At the end of the logic , call QThread::quit perhaps QThread::exit Exit the thread's event loop .

Two 、 example

Write an inheritance QObject Class :InheritQObject, The code is as follows :

#ifndef INHERITQOBJECT_H
#define INHERITQOBJECT_H

#include <QObject>
#include <QThread>
#include <QMutex>
#include <QMutexLocker>
#include <QDebug>

class InheritQObject : public QObject
{
    Q_OBJECT
public:
    explicit InheritQObject(QObject *parent = 0) : QObject(parent){

    }

    // Slot function for exit thread loop timing 
    void StopTimer(){
        qDebug()<<"Exec StopTimer thread = "<<QThread::currentThreadId();
        QMutexLocker lock(&m_lock);
        m_flag = false;
    }

signals:
    void ValueChanged(int i);

public slots:
    void QdebugSlot(){
        qDebug()<<"Exec QdebugSlot thread = "<<QThread::currentThreadId();
    }

    // Slot function 
    void TimerSlot(){
        qDebug()<<"Exec TimerSlot 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;
            }
        }
    }

private:
    bool m_flag;
    QMutex m_lock;
};

#endif // INHERITQOBJECT_H

mainwindow Main window class , The code is as follows :

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include "ui_mainwindow.h"
#include "InheritQObject.h"
#include <QThread>

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

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

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

        ui->setupUi(this);

        // establish QThread Thread objects and QObject Derived class object , Be careful : You don't need to set a parent class 
        m_th = new QThread();
        m_obj = new InheritQObject();
        
        // change m_obj Thread dependency of 
        m_obj->moveToThread(m_th);

        // Free up heap space resources 
        connect(m_th, &QThread::finished, m_obj, &QObject::deleteLater);
        connect(m_th, &QThread::finished, m_th, &QObject::deleteLater);
        // Set the field pointer to nullptr
        connect(m_th, &QObject::destroyed, this, &MainWindow::SetPtrNullptr);
        connect(m_obj, &QObject::destroyed, this, &MainWindow::SetPtrNullptr);
        // Connect other signal slots , Used to trigger the thread to execute the task in the slot function 
        connect(this, &MainWindow::StartTimerSignal, m_obj, &InheritQObject::TimerSlot);
        connect(m_obj, &InheritQObject::ValueChanged, this, &MainWindow::setValue);
        connect(this, &MainWindow::QdebugSignal, m_obj, &InheritQObject::QdebugSlot);

        // Start thread , Thread opens event loop by default , And the thread is in an event loop state 
        m_th->start();
    }

    ~MainWindow(){
        delete ui;
    }

signals:
    void StartTimerSignal();
    void QdebugSignal();

private slots:
    // Trigger thread execution m_obj Time slot function of 
    void on_startBt_clicked(){
        emit StartTimerSignal();
    }

    // Exit slot function 
    void on_stopBt_clicked(){
        m_obj->StopTimer();
    }

    // Detect thread state 
    void on_checkBt_clicked(){
        if(m_th->isRunning()){
            ui->label->setText("Running");
        }else{
            ui->label->setText("Finished");
        }
    }

    void on_SendQdebugSignalBt_clicked(){
        emit QdebugSignal();
    }

    // Exit thread 
    void on_ExitBt_clicked(){
        m_th->exit(0);
    }

    // Force exit thread 
    void on_TerminateBt_clicked(){
        m_th->terminate();
    }

    // Eliminate the wild pointer 
    void SetPtrNullptr(QObject *sender){
        if(qobject_cast<QObject*>(m_th) == sender){
            m_th = nullptr;
            qDebug("set m_th = nullptr");
        }

        if(qobject_cast<QObject*>(m_obj) == sender){
            m_obj = nullptr;
            qDebug("set m_obj = nullptr");
        }
    }

    // Respond to m_obj Signal to change the clock 
    void setValue(int i){
            ui->lcdNumber->display(i);
    }

private:
    Ui::MainWindow *ui;
    QThread *m_th;
    InheritQObject *m_obj;
};

#endif // MAINWINDOW_H

We can see from the above examples that , We don't have to rewrite QThread::run function , There is no need to explicitly call QThread::exec To start the thread's event loop , adopt QT Source code can be known , Just call QThread::start It will automatically execute QThread::exec To start the thread's event loop .
Subclass QThread How to create multithread , If run There is no dead loop or call in the function exec If you start the event loop , Even if it calls QThread::start Start thread , Finally, after a while , The thread still exits , be in finished The state of . So will this happen in this way ? Let's run the example above directly , Then check the status of the thread after a period of time :

The discovery thread is always running . Then let's talk about how to use the thread created in this way correctly, exit the thread and release the resources correctly .

3、 ... and 、 How to use threads correctly ( Signal slot ) And creating thread resources

(1) How to use threads correctly ?

If you need a thread to perform some behavior , Then we must use the signal slot mechanism correctly to trigger the slot function , The other ways to call the slot function are only executed in the old thread , Unable to achieve the desired effect . In multithreading, the details of the slot , It will be later 《 Cross thread slots 》 The article explains , Let's talk about how to use the signal slot to trigger the thread to execute the task .
From the above examples, we know that ,MainWindow The constructor uses connect Function will StartTimerSignal() Signals and InheritQObject::TimerSlot() The slot is bound , The code statement is as follows :

connect(this, &MainWindow::StartTimerSignal, m_obj, &InheritQObject::TimerSlot);

When you click 【startTime】 The button sends out StartTimerSignal() Signal time , This will trigger the thread to execute InheritQObject::TimerSlot() Slot function for timing .

From the above printed information, we know that ,InheritQObject::TimerSlot() The slot function is actually executed in a new thread . Inherit from above QThread It is also mentioned in the multithreading method , It's time to do it QThread::exit Or is it QThread::quit It's invalid , The exit signal will remain in the message queue , Just click 【stopTime】 Button to exit the thread while loop , And the thread enters the event loop ( exec() ) in , Will take effect , And exit the thread .

If you will 【startTime】 The button doesn't emit StartTimerSignal() The signal , I'm going to do it directly InheritQObject::TimerSlot() Slot function , What will be the result ? The code is modified as follows :

// Trigger thread execution m_obj Time slot function of 
void on_startBt_clicked(){
    m_obj->TimerSlot();
}

We'll find that the interface is stuck ,InheritQObject::TimerSlot() The slot function is in GUI Executed by the main thread , And that leads to this GUI The event loop of the interface cannot be executed , That is, the interface cannot be updated , So it's stuck . Therefore, it is effective to use the signal slot to trigger the thread work , Cannot call directly obj The member functions in it .

(2) How to create thread resources correctly ?

There are some resources that we can create directly in old threads ( That is to create resources without starting a thread through a slot ), It can also be used directly in new threads , For example bool m_flag and QMutex m_lock Variables are defined in the thread , It can also be used in new threads . But there are some resources , If you need to use , Then you have to create it in a new thread , For example, timer 、 Network socket, etc , Let's take a timer as an example , The code is modified as follows :

/********** stay InheritQObject Class QTimer *m_timer Member variables *****/
QTimer *m_timer;

/********** stay InheritQObject Constructor creation QTimer example *****/
m_timer = new QTimer();

/********** stay InheritQObject::TimerSlot Function USES m_timer*****/
m_timer->start(1000);

Run Click 【startTime】 Button time , The following error will be reported :

QObject::startTimer: Timers cannot be started from another thread

Thus we can see that ,QTimer It can't be used across threads , So modify the program as follows , take QTimer The instance creation is put into the thread to create :

/********* stay InheritQObject Class Init Slot function of , Put the resources that need to be initialized and created here ********/
public slots:
    void Init(){
        m_timer = new QTimer();
    }
    
/******** stay MainWindow Class InitSiganl() The signal , And bind the signal slot ***********/
// Add signal 
signals:
    void InitSiganl();
    
// stay MainWindow The constructor adds the following code 
connect(this, &MainWindow::InitSiganl, m_obj, &InheritQObject::Init); // Connect the signal slot 
emit InitSiganl(); // Signal , Start thread initialization QTimer resources 

such QTimer Timers are new threads , And it can be used normally . Network socket QUdpSocket、QTcpSocket It's OK to treat the resources in the same way .

Four 、 How to exit a thread correctly and release resources

(1) How to exit a thread correctly ?

The correct way to exit a thread , In fact, with the past 《 Subclass QThread Implement multithreading 》 in 《 How to exit a thread correctly and release resources 》 It's about the same in the section , It's about using quit and exit To exit the thread , Avoid using terminate To force the thread to end , Sometimes something unusual happens . For example, the above example , After starting , Just click 【terminate】 Button , The interface will be stuck .

(2) How to release thread resources correctly ?

In the past 《 Subclass QThread Implement multithreading 》 in 《 How to exit a thread correctly and release resources 》 The section also talks about , Never do it by hand delete QThread Class or derived class , 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 . So we need to Make good use of QObject::deleteLater  and  QObject::destroyed For memory management . Such as the code used in the above example :

// Free up heap space resources 
connect(m_th, &QThread::finished, m_obj, &QObject::deleteLater);
connect(m_th, &QThread::finished, m_th, &QObject::deleteLater);
// Set the field pointer to nullptr
connect(m_th, &QObject::destroyed, this, &MainWindow::SetPtrNullptr);
connect(m_obj, &QObject::destroyed, this, &MainWindow::SetPtrNullptr);

// Eliminate the wild pointer 
void SetPtrNullptr(QObject *sender){
    if(qobject_cast<QObject*>(m_th) == sender){
        m_th = nullptr;
        qDebug("set m_th = nullptr");
    }

    if(qobject_cast<QObject*>(m_obj) == sender){
        m_obj = nullptr;
        qDebug("set m_obj = nullptr");
    }
}

When we call the thread's quit perhaps exit function , And the thread reaches the state of the event loop , Then the thread will end and emit QThread::finished To trigger QObject::deleteLater Slot function ,QObject::deleteLater Will destroy the system for m_obj、m_th Object allocated resources . This is the time m_obj、m_th The pointer belongs to the wild pointer , So it needs to be based on QObject Class or QObject When the derived class object is destroyed QObject::destroyed Signal to set m_obj、m_th Pointer for nullptr, Avoid the existence of wild pointers .
Run the example above , And then click 【exit】 Button , The results are as follows :

5、 ... and 、 Summary

  • such QT Multithreading approach , Implement a simple 、 Flexible use , And the idea is clear , Relatively inherited from QThread Class approach is more reliable , This method is also officially recommended for implementation . If the thread is going to use an event loop , Using inheritance QObject The multithreading method is undoubtedly a better choice ;
  • establish QObject Derived class objects cannot have a parent class ;
  • call QThread::start Is the default start event loop ;
  • You have to use threads in a way that uses slots ;
  • Attention should be paid to the creation of cross line resources , for example QTimer、QUdpSocket And so on , If you need to use... In a child thread , Must be created in the child thread ;
  • Make good use of it QObject::deleteLater  and  QObject::destroyed For memory management  ;
  • Avoid using terminate Force exit thread , If you need to exit the thread , have access to quit or exit;

This article example source code address :https://gitee.com/CogenCG/QThreadExample.git

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