目錄
QT實現多線程有兩種方法:
1、繼承QThread類,並重寫run()函數;
---------這樣run()中的代碼就會運行在子線程中。
2、①寫一個對象worker,②聲明或new一個QThread變量mythread,③把這個對象移動到子線程中:worker.moveToThread(&mythread),④mythread.start()。其中①②不分先後, ③④不分先後。
---------這樣被信號觸發的worker對象的槽函數就會運行在子線程thread中,而直接顯式調用的槽函數仍運行在調用者的線程中(證據見以下實例)。
下面分別來說一下這兩種使用方法:
1、使用QThread::run()
參考:https://www.cnblogs.com/wangshaowei/p/8384474.html
https://blog.csdn.net/weixin_33716557/article/details/93720605
QT官方教程《Starting Threads with QThread》裏這樣說:https://doc.qt.io/archives/qt-4.8/threads-starting.html,
我們知道:每一個QThread對象都管理着一個線程,並通過start函數啓動這個線程,線程要執行的代碼都在run()裏面。run函數對一個線程來說,就好比main函數對一個應用程序。run函數的進入和返回,就相當於:子線程的啓動和結束。
run函數何時返回?一般來說,有3種常見的情形:
①run中的代碼走完一遍就返回了;
②run中有while(1)大循環,在大循環中有退出循環的flag變量,由外部程序置位這個flag,使大循環退出,從而run()返回;
③run的最後一行是事件循環exec(),也即程序會卡在這一行:this->exec(); 這種情況需要主動調用或者信號調用QThread::finish()或terminate()才能讓事件循環停下。從而讓run()返回。
實際編程時,到底用哪一種,看個人需求。
以①爲例:
//.h
class Worker : public QThread
{
public:
Worker(){;}
public slots:
void showMsg(void);
protected:
void run();
};
//.cpp
void Worker::showMsg()
{
qDebug() << "Worker::showMsg() thread id = " << this->currentThreadId();
}
void Worker::run()
{
this->showMsg();
}
運行結果可見:this->showMsg()這行代碼執行時,所在的線程與主線程不同。這就說明多線程實驗成功了。
需要注意的是,只有run()函數中的代碼段,會在子線程執行,如果在主線程直接調用(或者用信號觸發)某個MyThread對象的showMsg()函數,打印出的線程ID仍然是主線程的ID,並不能實現多線程的效果。
再補充一句:QThread類的run()的默認實現是這樣的:QThread::run() { this->exec(); }, 上述用法③實際上就是在仿照QThread的默認實現。
2、使用QObject::moveToThread
直接上例子:
//worker.h
#ifndef WORKER_H
#define WORKER_H
#include <QObject>
#include <QThread>
class Worker : public QObject
{
Q_OBJECT
public:
explicit Worker(QObject *parent = nullptr){;}
signals:
public slots:
void showThreadId(void);
};
#endif // WORKER_H
//worker.cpp
#include "worker.h"
#include <qDebug>
void Worker::showThreadId()
{
qDebug() << "Worker::showThreadId() = " << this->thread()->currentThreadId();
}
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <qDebug>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
worker = new Worker();
//connect(this, &MainWindow::showCmd, worker, &Worker::showThreadId);//位置1
childThread = new QThread();
childThread->start();//一定不要忘記啓動事件循環
worker->moveToThread(childThread);
//connect(this, &MainWindow::showCmd, worker, &Worker::showThreadId);//位置2
qDebug() << "main thread = " << this->thread()->currentThreadId();
worker->showThreadId();//主線程直接調用 worker->showThreadId()
emit showCmd();//主線程信號觸發 worker->showThreadId()
//注意看以上兩行代碼運行結果的區別
}
運行結果:
由以上結果可見,任何對象只要執行了moveToThread(),那麼該對象的所有槽函數就會在子線程執行(前提是,該槽函數是被信號觸發的),如果直接顯式調用這些槽函數,仍然會在運行在原線程,不會出現多線程的效果。
3、常見的錯誤編程方法
網上會看到很多這樣的例子,繼承QThread寫完了子類MyThread,並重寫run()之後,在run()調用MyThread類的槽函數或普通函數。這麼寫沒有問題,只要在run中被調用,就能實現多線程執行。
很多人會把主線程的信號,綁定到MyThread的槽函數,期望主線程發信號時,MyThread的槽函數能夠在子線程執行。結果打臉,並不能實現這種效果。因爲MyThread只是在管理子線程,MyThread的對象本身仍屬於原線程,所以這種情形下,MyThread的槽函數會在原線程被執行。
於是有人想出了這種辦法,把MyThread的對象也弄到子線程裏面去:在MyThread的構造函數中加上:this->moveToThread(this)。這樣竟然真的就實現了上一段話中所提到的期望。
不過這一方法QT官方不推薦,因爲從邏輯上、從代碼上看都非常另類,MyThread的對象在原線程被創建,用於管理子線程,結果該對象又被移動到了子線程中。感覺這種方法以後肯定會被QT在編譯環節給咔嚓掉。以後不要這麼寫就對了。
4、注意事項
某對象一旦被放進了多線程,那麼這個對象的所有槽函數中,或者run中,的代碼段就必須要做線程同步防護,否則程序極大概率會發生崩潰。
線程同步常用的有:信號量、互斥鎖等。