QThread 與 QObject的關係(QObject可以用於多執行緒,可以傳送訊號呼叫存在於其他執行緒的slot函式,但GUI類不可重入)
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,則輸出為:
http://blog.csdn.net/hai200501019/article/details/9748173