背景描述:
以前,繼承 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

