背景描述:
以前,繼承 QThread 重新實現 run() 函數是使用 QThread唯一推薦的使用方法。這是相當直觀和易於使用的。但是在工作線程中使用槽機制和Qt事件循環時,一些用戶使用錯了。Qt 核心開發人員Bradley T. Hughes, 推薦使用QObject::moveToThread 把它們移動到線程中。不幸的是, 以用戶反對這樣使用。Olivier Goffart, 前Qt 核心開發人之一, 告訴這些用戶你們不這樣做就錯了。最終這倆種用法我們都在QThread的文檔中發現 。
QThread::run() 是線程的入口點
從Qt文檔中我們可以看到以下內容:
A QThread instance represents a thread and provides the means to start() a thread, which will then execute the reimplementation of QThread::run(). The run() implementation is for a thread what the main() entry point is for the application.
Usage 1-0
在新的線程中執行一些代碼,繼承QThread 重新實現 run()函數接口。
For example
#include <QtCore> class Thread : public QThread { private: void run() { qDebug()<<"From worker thread: "<<currentThreadId(); } }; int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); qDebug()<<"From main thread: "<<QThread::currentThreadId(); Thread t; QObject::connect(&t, SIGNAL(finished()), &a, SLOT(quit())); t.start(); return a.exec(); }
結果輸出如下:
From main thread: 0x15a8
From worker thread: 0x128c
Usage 1-1
正因QThread::run() 是線程的入口, 所以很容易的理解它們, 並不是所有的代碼都在run()接口中被直接調用而不在工作線程中被執行。
接下來的例子中,成員變量 m_stop
在 stop() 和 run()都可被訪問到。考慮到前者將在主線程執行,后者在工作線程執行,互斥鎖或其它操作是有必要的。
#if QT_VERSION>=0x050000 #include <QtWidgets> #else #include <QtGui> #endif class Thread : public QThread { Q_OBJECT public: Thread():m_stop(false) {} public slots: void stop() { qDebug()<<"Thread::stop called from main thread: "<<currentThreadId(); QMutexLocker locker(&m_mutex); m_stop=true; } private: QMutex m_mutex; bool m_stop; void run() { qDebug()<<"From worker thread: "<<currentThreadId(); while (1) { { QMutexLocker locker(&m_mutex); if (m_stop) break; } msleep(10); } } }; #include "main.moc" int main(int argc, char *argv[]) { QApplication a(argc, argv); qDebug()<<"From main thread: "<<QThread::currentThreadId(); QPushButton btn("Stop Thread"); Thread t; QObject::connect(&btn, SIGNAL(clicked()), &t, SLOT(stop())); QObject::connect(&t, SIGNAL(finished()), &a, SLOT(quit())); t.start(); btn.show(); return a.exec(); }
結果輸出如下:
From main thread: 0x13a8
From worker thread: 0xab8
Thread::stop called from main thread: 0x13a8
你可以看到Thread::stop() 函數是在主線程中被執行的。
Usage 1-2 (錯誤的使用方式)
以上的例子很容易明白,但它不是那么直觀當事件系統(或隊列)中引進工作線程時。
例子如下, 我們應該做些什么,如果我們想在工作線程中周期性的做些動作?
- 在Thread::run()中創建一個QTimer
- 將超時信號連接到線程中的槽函數上
#include <QtCore> class Thread : public QThread { Q_OBJECT private slots: void onTimeout() { qDebug()<<"Thread::onTimeout get called from? : "<<QThread::currentThreadId(); } private: void run() { qDebug()<<"From worker thread: "<<currentThreadId(); QTimer timer; connect(&timer, SIGNAL(timeout()), this, SLOT(onTimeout())); timer.start(1000); exec(); } }; #include "main.moc" int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); qDebug()<<"From main thread: "<<QThread::currentThreadId(); Thread t; t.start(); return a.exec(); }
乍看起來代碼沒什么問題。當線程開始執行時, 我們在當前線程的事件處理中啟動了一個定時器。我們將 onTimeout()
連接到超時信號上。此時我們希望它在線程中執行?
但是執行結果如下:
From main thread: 0x13a4
From worker thread: 0x1330
Thread::onTimeout get called from?: 0x13a4
Thread::onTimeout get called from?: 0x13a4
Thread::onTimeout get called from?: 0x13a4
Oh, No!!! 它為什么在主線程中被調用了!
是不是很有趣?(接下來我們將要討論這是為什么)
如何解決這個問題
為了使槽函數工作在線程中, 有人嘗試在connect()函數中傳入參數 Qt::DirectConnection
connect(&timer, SIGNAL(timeout()), this, SLOT(onTimeout()), Qt::DirectConnection);
還有人嘗試在線程構造函數中添加如下功能。
moveToThread(this)
它們都會如期望的工作嗎. 但是 …
第二個用法的是錯誤的,
盡管看起來它工作啦,但很令人費解,這不是QThread 設計的本意(QThread 中所有實現的函數是被創建它的線程來調用的,不是在線程中)
實際上,根據以上表述,第一個方案是錯誤的。onTimeout() 是線程對象的一個成員函數,會被創建它的線程來調用。
它們都是錯誤的使用方法?!我們該如何做呢?
Usage 1-3
因為沒有一個線程類的成員是設計來被該線程調用的。所以如果我們想使用槽函數必須創建一個獨立的工作對象。
#include <QtCore> class Worker : public QObject { Q_OBJECT private slots: void onTimeout() { qDebug()<<"Worker::onTimeout get called from?: "<<QThread::currentThreadId(); } }; class Thread : public QThread { Q_OBJECT private: void run() { qDebug()<<"From work thread: "<<currentThreadId(); QTimer timer; Worker worker; connect(&timer, SIGNAL(timeout()), &worker, SLOT(onTimeout())); timer.start(1000); exec(); } }; #include "main.moc" int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); qDebug()<<"From main thread: "<<QThread::currentThreadId(); Thread t; t.start(); return a.exec(); }
結果如下:
From main thread: 0x810
From work thread: 0xfac
Worker::onTimeout get called from?: 0xfac
Worker::onTimeout get called from?: 0xfac
Worker::onTimeout get called from?: 0xfac
問題解決啦!
盡管運行的很好,但是你會注意到,當工作線程中運行在事件循環 QThread::exec()
中時,在QThread::run() 函數接口中沒有執行自身相關的事務。
所以我們是否可以將對象的創建從QThread::run()中移出, 此時, 槽函數是否依舊會被QThread::run()調用?
Usage 2-0
如果我們只想使用QThread::exec(), 默認情況下會被QThread::run() 調用, 那不需要子類化QThread。
- 創建一個工作對象
- 建立信號與槽的連接
- 將工作對象移至子線程中
- 啟動線程
#include <QtCore>
#include <QUdpSocket> class Worker : public QObject { Q_OBJECT
public:
void Work()
{
}
private:
QUdpSocket *socket; private slots: void onTimeout() { qDebug()<<"Worker::onTimeout get called from?: "<<QThread::currentThreadId(); } }; #include "main.moc" int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); qDebug()<<"From main thread: "<<QThread::currentThreadId(); QThread t; QTimer timer; Worker worker; QObject::connect(&timer, SIGNAL(timeout()), &worker, SLOT(onTimeout())); timer.start(1000); timer.moveToThread(&t); worker.moveToThread(&t); t.start(); return a.exec(); }
結果是:
From main thread: 0x1310
Worker::onTimeout get called from?: 0x121c
Worker::onTimeout get called from?: 0x121c
Worker::onTimeout get called from?: 0x121c
正如所預料的,槽函數沒有在主線程中執行。
在此例中,定時器和工作對象都被移至子線程中,實際上,將定時器移至子線程中是沒有必要的。
Usage 2-1
在上面的例子當中將 timer.moveToThread(&t);
這一行注釋掉。
int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); qDebug()<<"From main thread: "<<QThread::currentThreadId(); QThread t; QTimer timer; Worker worker; QObject::connect(&timer, SIGNAL(timeout()), &worker, SLOT(onTimeout())); timer.start(1000); // timer.moveToThread(&t); worker.moveToThread(&t); t.start(); return a.exec(); }
不同之處如下:
在上面的例子中,
- 信號
timeout()
是由子線程發送出去的。 - 定時器和工作對象是在同一線程當中的,它們的連接方式直接連接。
- 槽函數的調用和信號的觸發是在同一線程中的。
在本例中,
- 信號
timeout()
是由主線程發出的。 - 定時器和工作對象是在不同的線程中,它們的連接方式隊列連接。
- 槽函數是在子線程中被調用。
- 由於隊列連接機制的存在,可以安全的將信號和槽函數在不同的線程中連接起來。如果所有的跨線程通信都通過隊列連接方式,那么多線程的互斥防范機制將不在需要。
總結
- 子類化QThread ,實現 run()函數接口是很直觀的,也存在很多不錯的方法去子類化QThread,但是在工作線程中處理事件處理並不是一件簡單的事。
- 在事件處理存在時使用對象時將它們移至線程中時很簡單的,這樣將事件處理和隊列連接的細節給隱藏了。
QObject::thread()
可以查詢一個QObject
的線程依附性,通過QThread::currentThread()可以查詢當前運行所屬線程,都非常有用。
QThread *thread; thread=new QThread(this); qDebug()<<"1:"<<commUdpClient->thread(); commUdpClient->moveToThread(thread); thread->start(); qDebug()<<"2:"<<commUdpClient->thread();
輸出:
說明對象commUdpClient確實發生所在線程的變化。但是值得注意的是,commUdpClient對象中的
QUdpSocket *socket;
QUdpSocket *socket2;
QUdpSocket socket3; 前兩個指針在構造函數里和槽函數里初始化,所屬線程是有很大區別的。如下面代碼所示,在構造函數里初始化socket2,意味着屬於主線程,不管commUdpClient是否移到子線程,它還是屬於主線程;而socket是在commUdpClient對象移到子線程后,再在槽函數中初始化,則屬於子線程;socket3默認在構造函數里初始化,即使構造函數沒有顯式初始化,但是也屬於主線程,其它如int、char等基礎數據變量也是屬於主線程的。所以在利用moveToThread方式使用多線程的情況下,commUdpClient類聲明中類類型對象建議都聲明為指針,然后都在某個槽函數中初始化,而基礎數據變量如int、char等建議聲明為private,如果需要改變則利用屬性的方式get、set來改變,實在需要使用public,則需要加鎖,不然會出錯。

所以要求A的初始化過程放到某個槽函數中進行,包括connect的過程,然后由線程ThreadParent中調用A的對象C通過信號的方式觸發該槽。A其它的槽要想在對象C中調用也需要通過信號的方式,如果通過直接調用A.XX()方式調用槽函數或者其它函數,則仍舊是在線程ThreadParent中運行,非在線程ThreadSon中運行。切記切記,具體看以下代碼:
對象B的源碼.h
//CommUdpClient.h #include <QObject> #include <QUdpSocket> class CommUdpClient : public QObject { Q_OBJECT public: explicit CommUdpClient(QObject *parent = 0); ~CommUdpClient(); QUdpSocket *socket;
QUdpSocket *socket2;
QUdpSocket socket3; private: QHostAddress serverAddress; quint16 serverPort; public: //發送報文 void send(const QByteArray &msg); signals: //接收完報文后觸發的信號,用於上層觸發相應的槽,獲得報文 void sigRevedMsg(QByteArray msg,const QStringList ¶s); public slots: //報文發送完成信號bytesWritten對應的槽 void sltSent(qint64 bytes); //接收報文,對應信號readyRead的槽 QByteArray sltReceive(); //通訊參數設置,打開連接 void sltOpenConnect(const QStringList ¶s); };
對象B的源碼.CPP
#include "commudpclient.h" #include <qstringlist.h> #include <QMetaEnum> #include <QDebug> #include <QThread> CommUdpClient::CommUdpClient(QObject *parent) : QObject(parent) { qDebug()<<"udp current thread 1:"<<QThread::currentThread(); //錯誤 //socket=new QUdpSocket; //connect(socket,SIGNAL(bytesWritten(qint64)),this,SLOT(sltSent(qint64))); //connect(socket,SIGNAL(readyRead()),this,SLOT(sltReceive()));
socket2=new QUdpSocket;
qDebug()<<"socket2 thread:"<<socket2->thread(); } CommUdpClient::~CommUdpClient() { if(socket!=NULL) { if(socket->isOpen()) { socket->close(); } delete socket; socket=NULL; } } void CommUdpClient::sltOpenConnect(const QStringList ¶s) { qDebug()<<"udp current thread 2:"<<QThread::currentThread(); //正確 socket=new QUdpSocket();//這里加this和不加this是有區別的

這是打印出來的socket的parent,是有區別的,不過這里的CommUdpClient也是頂層的,無parent的,不然是無法移動到子線程中的,會報錯

,具體在對象B的源碼中描述。
connect(socket,SIGNAL(bytesWritten(qint64)),this,SLOT(sltSent(qint64))); connect(socket,SIGNAL(readyRead()),this,SLOT(sltReceive()));
//測試
qDebug()<<"socket thread:"<<socket->thread();
qDebug()<<"socket2 thread:"<<socket2->thread();
qDebug()<<"scoket3 thread:"<<socket3.thread();
qDebug()<<this<<", thread:"<<this->thread();
serverAddress.setAddress(paras[0]); serverPort=paras[1].toUShort(); socket->bind(paras[2].toUShort()); } void CommUdpClient::send(const QByteArray &msg) {
qDebug()<<"udp send thread:"<<QThread::currentThread(); if(!serverAddress.isNull()) { socket->writeDatagram(msg,serverAddress,serverPort); qDebug()<<"client send end"; } } void CommUdpClient::sltSent(qint64 bytes) { } QByteArray CommUdpClient::sltReceive() { qDebug()<<"server start receive..."; while (socket->hasPendingDatagrams()) { QByteArray datagram; datagram.resize(socket->pendingDatagramSize()); QHostAddress sender; quint16 senderPort; socket->readDatagram(datagram.data(), datagram.size(), &sender, &senderPort); qDebug()<<datagram; QStringList sl; sl.append(sender.toString()); sl.append(QString::number(senderPort)); emit sigRevedMsg(datagram,sl); return datagram; } }
對象A的源碼.h
#ifndef COMMUNICATION_H #define COMMUNICATION_H #include <QObject> #include <QThread> #include <qstringlist.h> #include "commtcpclient.h" class Communication : public QObject { Q_OBJECT public: explicit Communication(QObject *parent = 0); virtual ~Communication(); public: CommUdpClient *commUdpClient; QThread *thread; public: void readData(); void writeData(); signals: void sigThreadConnect(const QStringList ¶s); public slots: void sltRevedMsg(QByteArray msg,const QStringList ¶s); }; #endif // COMMUNICATION_H
對象B的源碼.cpp
#include "communication.h" #include <QCoreApplication> #include <QDebug> #include <QTime> Communication::Communication(QObject *parent) : QObject(parent) { qDebug()<<"current thread:"<<QThread::currentThread(); commUdpClient=new CommUdpClient();//這里不能加this,不然是不允許移到子線程中,報錯

connect(commUdpClient,SIGNAL(sigRevedMsg(QByteArray,QStringList)),this,SLOT(sltRevedMsg(QByteArray,QStringList))); connect(this,SIGNAL(sigThreadConnect(QStringList)),commUdpClient,SLOT(sltOpenConnect(QStringList))); thread=new QThread(this);
qDebug()<<"1:"<<commUdpClient->thread(); commUdpClient->moveToThread(thread);
qDebug()<<"2:"<<commUdpClient->thread();
thread->start();
qDebug()<<"3:"<<commUdpClient->thread(); } Communication::~Communication() { delete commUdpClient; thread->quit(); delete thread; } void Communication::readData() { QString sz="192.168.1.186:1234:1235"; QStringList sl=sz.split(':'); emit sigThreadConnect(sl);//正確用法
//commUdpClient->send("error");//不正確的用法,這個是直接在對象B的線程中運行的
} void Communication::writeData() { qDebug()<<"xxxx"; } void Communication::sltRevedMsg(QByteArray msg) { qDebug()<<"communication end,receive msg:"<<msg; } void Communication::sltRevedMsg(QByteArray msg, const QStringList ¶s) { }
遠行結果:
//commUdpClient->send("error");//不正確的用法,這個是直接在對象B的線程中運行的

具體的參考資料:http://blog.csdn.net/sydnash/article/details/7425947 一種使用QThread線程的新方法QObject::moveToThread
http://blog.csdn.net/lainegates/article/details/9701215
http://mobile.51cto.com/symbian-268690_1.htm
關於: QObject: Cannot create children for a parent that is in a different thread錯誤
參考:http://blog.chinaunix.net/uid-26808060-id-3355816.html
class TcpComm:public QThread { Q_OBJECT public: TcpComm(const QString &iAddrStr, quint16 iPort); ~TcpComm(); ........ private: ....... TcpClient*mTcpClient; }; TcpComm::TcpComm(const QString &iAddrStr, quint16 iPort):mAddr(iAddrStr), mPort(iPort) { mIsStop = false; mTcpClient = new TcpClient(); start(); } 以上程序在運行時報QObject: Cannot create children for a parent that is in a different thread錯誤。 將原構造函數中的mTcpClient = new TcpClient();放到run()中問題解決。 TcpComm::TcpComm(const QString &iAddrStr, quint16 iPort):mAddr(iAddrStr), mPort(iPort) { mIsStop = false; start(); } void TcpComm::run() { mTcpClient = new TcpClient(); ........ } 查了查,原因應該是,在QThread中定義的所有東西都屬於創建該QThread的線程。所以在構造函數中初始化的mTcpClient應該是屬於父線程的,那么在run中調用時就屬於跨線程調用。所以把mTcpClient放到run中初始化就屬於線程的了,調用時就不會出現跨線程調用的問題。
另外:QThread中寫的所有函數都應該在創建它的線程中調用,而不是開啟QThread的線程
其它參考資料:http://blog.csdn.net/zhangbinsijifeng/article/details/52299926 qt中的線程 擁有權 一個對象屬於哪個線程
http://blog.csdn.net/an505479313/article/details/50351745 QThread使用——關於run和movetoThread的區別
http://blog.csdn.net/dbzhang800/article/details/6554104 Qt 線程基礎(QThread、QtConcurrent等)
https://www.cnblogs.com/liushui-sky/p/5829563.html?tdsourcetag=s_pcqq_aiomsg