最近抽空研究了下QThread,使用起來方式多種多樣,但是在使用的同時,我們也應該去了解Qt的線程它到底是怎么玩兒的。
Qt的幫助文檔里講述了2種QThread的使用方式,一種是moveToThread,另一種是繼承QThread實現run方法,下面我們分別來分析下
一、moveToThread
首先我們來先分析move這種方式,他的使用可能像下面這樣
class Worker : public QObject
{
public slots:
void doWork(const QString &) {
emit resultReady(result);
}
};
class Controller : public QObject
{
QThread workerThread;
public:
Controller() {
Worker *worker = new Worker;
worker->moveToThread(&workerThread);
connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);
connect(this, &Controller::operate, worker, &Worker::doWork);
connect(worker, &Worker::resultReady, this, &Controller::handleResults);
workerThread.start();
}
~Controller() {
workerThread.quit();
workerThread.wait();
}
};
這是一個標准的多線程使用方式,復雜的邏輯操作我們可以放在Worker對象的槽函數中進行,因為只有槽函數是在工作線程中執行的,下面我記錄了各個函數執行時所在的線程ID
由於線程ID是每次會發生編號,可能每個人測試的結果不一樣
- Worker(): 0x4c34 主線程
- doWork(): 0x40c8 工作線程
- handleResults(): 0x4c34 主線程
- ~Worker(): 0x40c8 工作線程
細心的同學就會發現了,Worker對象的構造函數和析構函數不在同一個線程里邊:Worker對象的事件循環已經放到子線程中了,Worker對象刪除時,是工作線程通過拋出DeferredDelete事件執行的
下面結合我自己之前的一些使用理解,來分析下moveToThread是如何運作的:
假設有這么一種場景,需要把對象obj從線程A移動到線程B
首先我自己看了Qt的這個函數源碼,這里把他翻譯成為了白話文,我們大家可以來看下
1、一些異常判斷
- 確認不在同一個線程里
- 移動的對象不能有父類
- 不能移動Widget窗體
- 支持移動一個無所屬線程的對象到指定線程
- 對象不在C線程時,C線程不能把對象移動到B線程,只有A線程可以
2、moveToThread_helper
- 構造ThreadChange事件,發送給自己
- 迭代所有子對象,並執行moveToThread_helper方法
3、setThreadData_helper
- 循環遍歷,把線程A中obj對象的所有事件移動到B線程中
- 如果移動了新事件到線程B中,則我們需要喚醒B線程,讓他去派發事件
- 迭代所有子對象,並執行setThreadData_helper方法
二、繼承QThread
假設說我們繼承QThread實現了一個UsThread,使用起來可能像這樣
UsThread thd;
經過我的實踐,很可惜,除了run函數以外,所有的函數執行,包括對象都在主線程中
如果你想着thd.moveToThread這么干,那么可能會被打死
結論:個人推薦使用moveToThread這種方式進行子線程編寫
更詳細的測試結果可以參考
QThread使用——關於run和movetoThread的區別