QThread 繼承 QObject.。它可以發送started和finished信號,也提供了一些slot函數。
QObject.可以用於多線程,可以發送信號調用存在於其他線程的slot函數,也可以postevent給其他線程中的對象。之所以可以這樣做,是因為每個線程都有自己的事件循環。
在進行下面的講解之前,應該了解的重要的一點是:QThread 對象所在的線程,和QThread 創建的線程,也就是run()函數執行的線程不是同一個線程。QThread 對象所在的線程,就是創建對象的線程。我們通過一個例子說明更能清楚一點:
MyThread::MyThread(QObject *parent /* = NULL */):QThread(parent)
{
qDebug()<<"MyThreadobject currentThreadId :"<<QThread::currentThreadId();
}
void MyThread::run()
{
qDebug()<<"run() currentThreadId : "<<QThread::currentThreadId();
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MyThread thread;
qDebug()<<"mainThread : "<<QThread::currentThreadId();
thread.start();
returna.exec();
}
輸出結果:MyThread所在的線程就是主線程,run()函數是新開的線程。
QObject Reentrancy
QObject.是可重入的,它的大多數非GUI子類,例如QTimer, QTcpSocket, QUdpSocket and QProcess都是可重入的,使得這些類可以同時用於多線程。需要注意的是,這些類設計為從一個單一的線程創建和使用的,在一個線程創建對象,而從另外一個線程調用對象的函數並不能保證行得通。有三個限制需要注意:
1. QObject的子對象必須在創建其parent的線程中創建。這意味着,你不能把QThread對象作為parent傳遞給創建在線程中的對象,因為QThread 對象本身在另外一個線程中創建。
2. 事件驅動對象只能用於單線程。尤其是在定時器機制和網絡模塊。例如,你不能在不是對象所處的線程start一個計時器或者鏈接一個secket。簡單的說就是,你不能在線程A創建了一個計時器timer,然后在線程B從啟動timer。
我們可以驗證一下:
class MyThread : publicQThread
{
Q_OBJECT
public:
MyThread(QObject *parent = NULL);
~MyThread();
public slots:
voidtimeOutSlot();
protected:
voidrun();
QTimer *m_pTimer;
};
MyThread::MyThread(QObject*parent /* = NULL */):QThread(parent)
{
m_pTimer = newQTimer(this);
qDebug()<<"MyThreadobject currentThreadId :"<<QThread::currentThreadId();
connect(m_pTimer,SIGNAL(timeout()),this,SLOT(timeOutSlot()));
}
void MyThread::timeOutSlot()
{
qDebug()<<"timer timeout ";
}
MyThread::~MyThread()
{
}
void MyThread::run()
{
m_pTimer->start(500);
qDebug()<<"run() currentThreadId : "<<QThread::currentThreadId();
qDebug( "finish!");
}
intmain(int argc, char*argv[])
{
QApplication a(argc, argv);
MyThread thread;
qDebug()<<"mainThread : "<<QThread::currentThreadId();
thread.start();
returna.exec();
}
Timeout函數並沒有被調用。我們還發現有多了一行輸出:QObject::startTimer: timers cannot be startedfrom another thread
跟蹤timer的start源碼,我們發現:
void QEventDispatcherWin32::registerTimer(int timerId, intinterval, QObject *object)
{
if (timerId< 1 || interval < 0 || !object) {
qWarning("QEventDispatcherWin32::registerTimer:invalid arguments");
return;
}
else if(object->thread() != thread() || thread() != QThread::currentThread())
{
//判斷object的thread,也就是object所在的thread,不等於當前的線程就返回了
qWarning("QObject::startTimer:timers cannot be started from another thread");
return;
}
。。。。。
}
3. 你必須保證在線程中創建的對象要在線程銷毀前delete。這很容易做到,只要是在run()函數棧里創建的對象就行。
盡管 QObject 是可重入的,但是GUI類,特別是QWidget 和它的子類都是不可重入的。它們只能在主線程中用。就如前面提到的, QCoreApplication::exec()必須從主線程進行調用。
Per-Thread Event Loop
每個線程都有自己的事件循環。起始的線程用QCoreApplication::exec()開啟事件循環。其他的線程用QThread::exec()開始事件循環。與 QCoreApplication一樣, QThread也提供了 exit(int) 函數 和 quit() 槽函數。
線程里的事件循環,使得可以在線程里使用需要事件循環的非GUI類,例如(QTimer, QTcpSocket, and QProcess).。也可以把任意的線程的信號連接到特定線程的槽。
QObject實例存在於創建實例的線程中,發送給實例事件也是有線程的事件循環實現的。可以用 QObject::thread().獲取對象存活於哪個線程。
MyThread::MyThread(QObject*parent /* = NULL */):QThread(parent)
{
m_pTimer = newQTimer(this);
qDebug()<<"MyThreadobject currentThreadId :"<<QThread::currentThread();
QObject obj1;
obj1.thread();
qDebug()<<"obj1live in the thread :"<<obj1.thread();
connect(m_pTimer,SIGNAL(timeout()),this,SLOT(timeOutSlot()));
//QThread::start();
}
void MyThread::run()
{
QObject obj2;
obj2.thread();
qDebug()<<"button2live in the thread :"<<obj2.thread();
//m_pTimer->start(500);
qDebug()<<"run() currentThreadId : "<<QThread::currentThread();
qDebug( "finish!");
}
這個再一次說明了,對象所處的線程就是創建它的線程。
注意:對於那些在QApplication之前創建的對象,QObject::thread() 返回0。這意味着,主線程只處理發送給那些對象的事件,那些沒有thread的對象是不做任何的事件處理的。使用QObject::moveToThread()函數可以改變對象及其子對象的線程關聯度,說白了就是把對象從當前的線程移到另外的線程里。但是如果一個對象已經有了parent,那是不能move了。
調用delete刪除處於另外一個線程的QObject對象是不安全的。除非你能保證對象當前不是在進行事件處理。應該用QObject::deleteLater()替代,並且將發出一個DeferredDelete事件,這個事件會最終會被對象的線程的時間循環所捕獲。
如果沒有時間循環,就不會有事件傳遞給對象。例如,如果你在一個線程中創建了一個QTimer對象,但是不調用exec(),,那么QTimer永遠不會發出timeout()信號,調用eleteLater() 也不起作用。
void MyThread::run()
{
m_pTimer = newQTimer();
m_pTimer->start(500);
connect(m_pTimer,SIGNAL(timeout()),this,SLOT(timeOutSlot()));
qDebug()<<"run() currentThreadId : "<<QThread::currentThread();
this->exec();
//qDebug("finish!" );
}
void MyThread::timeOutSlot()
{
qDebug()<<"timer timeout ";
//m_pTimer->stop();
}
void MyThread::run()
{
m_pTimer = newQTimer();
m_pTimer->start(500);
connect(m_pTimer,SIGNAL(timeout()),this,SLOT(timeOutSlot()));
qDebug()<<"run() currentThreadId : "<<QThread::currentThread();
//this->exec();
//qDebug("finish!" );
}
如果注釋//this->exec();,timeOutSlot()將不會被調用。
還有一點要注意的:QTimer對象也不能在另外的線程stop的。如果把timeOutSlot里的m_pTimer->stop();取消注釋。會看到一行輸出:QObject::killTimer: timers cannot be stopped fromanother thread
源碼中:
bool QEventDispatcherWin32::unregisterTimer(int timerId)
{
if (timerId< 1) {
qWarning("QEventDispatcherWin32::unregisterTimer:invalid argument");
return false;
}
QThread *currentThread = QThread::currentThread();
//判斷timer所處的線程與當前的線程是否一致。
if(thread() != currentThread)
{
qWarning("QObject::killTimer:timers cannot be stopped from another thread");
return false;
}
。。。。
}
你可以用QCoreApplication::postEvent()函數在任意時間給任意線程中的任意對象發送事件。事件自動被創建object的線程的事件循環分發。所以的線程都支持事件過濾器,唯一的限制就是,監視對象必須與被監視對象處於同一個線程。同樣的,QCoreApplication::sendEvent() 只能用來給與調用QCoreApplication::sendEvent() 函數處於同一個線程的對象發送事件。說白了就是,QCoreApplication::sendEvent() 不能給處於另外線程的對象發送事件。
Accessing QObjectSubclasses from Other Threads
QObject 和它所有的子類都不是線程安全的。這包含了整個事件發送系統,需要記住的很重要的一點是:事件循環可能正在給一個對象發送一個事件,同時你可能從別的線程訪問該對象。
如果你調用了一個不是出於當前線程QObject 子類對象的一個函數,而此時對象可能接收一個事件,你必須用一個mutex保護對象的內在的數據。否則,可能引起程序崩潰或者未定義的行為。
與其他的對象一樣,QThread對象存活於創建對象的線程中,而不是存在於QThread::run() 線程。這點在前面講到了。在自定義 QThread子類中提供slot函數是不安全的,除非你用一個mutex保護了成員變量。然而,你可以在實現的 QThread::run() 里發出信號,因為信號發送是線程安全的。
Signals and Slots AcrossThreads
Qt支持了幾種信號--槽的連接方式:
1. Auto Connection (默認):如果如果信號的發送方與接收方是處於同一個線程,這個連接就是 Direct Connection,否則就跟 Queued Connection一樣。
2. Direct Connection :當信號發出之后,槽會立即被調用。槽函數是在信號發送方的線程中運行的,不需要接收方的線程。
3. Queued Connection:當控制權回到接收方線程時調用槽函數。槽函數是在接收方的線程中運行的。
4. Blocking Queued Connection :調用方式跟 Queued Connection一樣,區別在於,當前線程會被阻塞直到槽函數返回。
5. Unique Connection :這種方式跟 Auto Connection一樣,但是只有當不存在一個相同的連接時才會創建一個連接。如果已經存在相同的連接,則不會創建連接,connect()返回false。
可以在connect()添加參數指定連接類型。需要注意的一點是:如果信號發送方和接收方處於不同的線程,而且接收方線程運行着一個事件循環,此時用Direct Connection是不安全,原因跟調用一個對象的函數,而這個對象處於另外的線程,那樣的調用是不安全。
QObject::connect() 本身是線程安全的。
下面通過結果例子驗證一下:
class Receiver:publicQObject
{
Q_OBJECT
public:
voidsendmes()
{
emitemitSignal("emit message from A To B");
}
Receiver()
{
}
protected slots:
voidmessageSlot(QString mes)
{
qDebug()<<mes;
}
signals:
voidemitSignal(QString mes);
private:
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Receiver objA,objB;
QObject::connect(&objA,SIGNAL(emitSignal(QString)),&objB,SLOT(messageSlot(QString)));
qDebug()<<"beforeemitsignal ";
objA.sendmes();
qDebug()<<"afteremitsignal ";
returna.exec();
}
objA,objB;出於同一個線程,所以connect的連接類型是Direct Connection
由輸出我們可以看出執行順序,
如果我們寫了兩句連接:
QObject::connect(&objA,SIGNAL(emitSignal(QString)),&objB,SLOT(messageSlot(QString)));
QObject::connect(&objA,SIGNAL(emitSignal(QString)),&objB,SLOT(messageSlot(QString)));
就會相應的有兩句消息輸出:
如果指定了連接類型Qt::UniqueConnection ,就會只有一句消息輸出了。
QObject::connect(&objA,SIGNAL(emitSignal(QString)),&objB,SLOT(messageSlot(QString)),Qt::UniqueConnection );
QObject::connect(&objA,SIGNAL(emitSignal(QString)),&objB,SLOT(messageSlot(QString)),Qt::UniqueConnection);
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QThread *thread = new QThread;
thread->start();
Receiver objA,objB;
objB.moveToThread(thread);
QObject::connect(&objA,SIGNAL(emitSignal(QString)),&objB,SLOT(messageSlot(QString)) );
qDebug()<<"beforeemitsignal ";
objA.sendmes();
qDebug()<<"afteremitsignal ";
returna.exec();
}
如果我們把objB放到另外的線程,connect的連接類型應該是Queued Connection 。
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QThread *thread = new QThread;
thread->start();
Receiver objA,objB;
objB.moveToThread(thread);
QObject::connect(&objA,SIGNAL(emitSignal(QString)),&objB,SLOT(messageSlot(QString)) ,Qt::BlockingQueuedConnection);
qDebug()<<"beforeemitsignal ";
objA.sendmes();
qDebug()<<"afteremitsignal ";
returna.exec();
}
顯示的指定連接類型為Qt::BlockingQueuedConnection,則輸出為: