Past links :
- 《QThread Source analyses 》
- 《 Subclass QThread Implement multithreading 》
This article example source code address :https://gitee.com/CogenCG/QThreadExample.git
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