工作需要,前段時間要寫TCP連接的模塊,因爲連接本身可能是一個耗時的操作,而且要實現自動重連功能,所以必須要用到多線程,因此有機會學習QThread,現將體會成文。
首先是QThread::exec(),這個函數將進入當前線程的事件循環(網上很多文章都寫成時間循環),調用這個函數後將會阻塞線程,這時線程中的事件傳遞纔有效,信號與槽的連接纔有效,此時信號發射後槽函數纔會被調用。當執行了線程的quit()或exit()函數時exec()函數才返回。
看程序
//mythread.h
#ifndef MYTHREAD_H
#define MYTHREAD_H
#include <QThread>
class QTimer ;
class MyThread : public QThread
{
Q_OBJECT
public:
explicit MyThread(QObject *parent = 0);
protected:
void run();
public slots:
void slotFunc();
private:
QTimer *timer;
};
#endif // MYTHREAD_H
//mythread.cpp
#include "mythread.h"
#include <QtDebug>
#include <QTimer>
MyThread::MyThread(QObject *parent) :
QThread(parent)
{
}
void MyThread::run()
{
timer = new QTimer;
timer->setSingleShot(true);
timer->start(5000);
qDebug() << timer << QThread::currentThreadId();
connect(timer, SIGNAL(timeout()), this, SLOT(slotFunc()));
exec();
qDebug() << "********************";
}
void MyThread::slotFunc()
{
qDebug() << timer << QThread::currentThreadId();
qDebug() << "#################";
}
//main.cpp
#include <QApplication>
#include "mythread.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MyThread obj;
obj.start();
return a.exec();
}
程序運行輸出結果:
QTimer(0xb51019e8)3047508848
QTimer(0xb51019e8)3078874896
#################
由輸出結果可知發射信號和槽函數執行正常,這時可能就有人這樣思考,exec()是阻塞線程的,同樣死循環也是阻塞的,而且更容易被我們控制(我就是這樣想的),下面我們嘗試一下,將run()函數中的exec()替換爲
int time = 5;
while(time > 0) {
sleep(2);
time--;
}
然後運行程序,輸出:
QTimer(0xb50019e8)3046460272
********************
由此可見,要真正實現子線程中的信號與槽的連接,必須得執行exec()函數進入子線程的事件循環。其實這個函數我們並不陌生,Qt的main函數中基本上都有如下的程序: QApplication a(argc, argv);
... //do something
return a.exec();
其實這段程序就是要進入主線程的事件循環,這樣事件纔會被傳遞處理,若寫成return 0;,程序會異常退出。
細心的讀者可能已經發現,在第一個程序的輸出結果中,timer的地址相同,但它卻處於兩個不同的線程中,也就是說,timer在一個線程中創建,然後在另一個線程中執行,如果在這個線程中執行刪除操作則可能會出現意想不到的結果,除非mutex用的好。這時如果將slotFunc()函數修改爲
void MyThread::slotFunc()
{
qDebug() << timer << QThread::currentThreadId();
qDebug() << "#################";
timer->start(5000);
}
然後執行程序,
QTimer(0xb51019e8) 3047361392
QTimer(0xb51019e8)3078637328
#################
程序僅僅輸出一次,而不是像預期的那樣,每隔5s輸出一次,這就是因爲timer是處在兩個不同的線程中,而如果我們改變一下信號與槽的連接方式:
connect(timer, SIGNAL(timeout()), this, SLOT(slotFunc()), Qt::DirectConnection);
運行程序,有
QTimer(0xb50019e8)3046460272
QTimer(0xb50019e8)3046460272
#################
QTimer(0xb50019e8)3046460272
#################
QTimer(0xb50019e8)3046460272
#################
這樣程序就會每隔5s輸出一次,這時timer已經處於同一個線程了(可以通過改變信號與槽的連接方式改變槽函數的執行方式,不明白請了解Qt信號與槽的連接方式)。
另外,在Qt多線程編程中,如果用到QTcpSocket類,可能會出現如下警告:
QObject: Cannot create children for aparent that is in a different thread.
(Parent is QTcpSocket(0x8373b28), parent'sthread is QThread(0x81c9298),current thread is ConnThread(0x8373b18)
這是因爲創建和使用QTcpSocket對象不是在同一個線程,只要參照上面給出的博客的方法,使得在同一個線程創建和使用QTcpSocket對象就能避免警告。
最後,若有如下程序(.h文件未給出):
#include "newobject.h"
#include <QTcpSocket>
NewObject::NewObject(QObject *parent) :
QObject(parent)
{
readBuffer = new char[1500];
}
void NewObject::createVariables()
{
socket = new QTcpSocket;
connect(socket, SIGNAL(readyRead()), this, SLOT(readData()));
}
void NewObject::readData()
{
while(socket->bytesAvailable()) {
int size = socket->read(readBuffer, 1500);
printf("size = %d\n", size);
}
}
QThread *newThread = new QThread;
newObj = new NewObject;
newObj->moveToThread(newThread);
newThread->start();
當執行下面代碼時,雖然不會報告任何警告或出錯信息,但是在執行newObj=newNewObject;時會執行readBuffer=newchar[1500];語句,這時程序執行在主線程中,也就是說,readBuffer是在主線程中創建的內存區域,而使用是在子線程中,這樣程序在執行過程中會出現意想不到的錯誤而導致崩潰(我本人爲此崩潰了許久,通過後面給出的方法修改終於穩定),因此要保證通過new創建對象和使用在同一個線程,可以將readBuffer=newchar[1500];置於createVariables()函數中,然後添加:
connect(newThread,SIGNAL(started()), newObj, SLOT(createVariables()));
同樣,通過下面語句來銷燬對象:
connect(newThread,SIGNAL(finished()), newObj, SLOT(releaseVariables()));
其中 releaseVariables()函數執行delete操作。好了,就是這些,多線程編程本身就比較複雜,而Qt封裝了許多實現後若對其用法不是很明確會遇到各種各樣的異常,出現異常,很多時候是由於我們使用方法不對,而不是對多線程理解有誤,無論如何,我們只有在使用中摸索。
寫了這麼多,希望能夠幫到大家,不對之處歡迎大家指正~~~~~~