QT多線程QThread::run()與QObject::moveToThread()標準用法

目錄

1、使用QThread::run()

2、使用QObject::moveToThread

3、常見的錯誤編程方法

4、注意事項


 

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中,的代碼段就必須要做線程同步防護,否則程序極大概率會發生崩潰。

線程同步常用的有:信號量、互斥鎖等。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章