爲徹底搞清原因並解決問題,在查閱大量資料和Qt文檔之後,理清了其中的機制,也對多線程編程中的QObject對象創建以及connect執行有更清楚的認識:
1. 一個對象的線程就是創建該對象時的線程,而不論該對象的定義是保存在那個線程中;
2. QObject的connect函數有幾種連接方式,
a) DirectConnection,信號發送後槽函數立即執行,由sender的所在線程執行;
b) QueuedConnection,信號發送後返回,相關槽函數由receiver所在的線程在返回到事件循環後執行;
c) 默認使用的是Qt::AutoConnection,當sender和receiver在同一個線程內時,採用DirectConnection的方式,當sender和receiver在不同的線程時,採用QueuedConnection的方式。
爲了更清楚的理解這些問題,在此特編了個小例子說明一下。首先定義一個從QObject繼承的類SomeObject,包含一個信號someSignal和一個成員函數callEmitSignal,此函數用於發送前面的someSignal信號。定義如下:
- {
- Q_OBJECT
- public:
- SomeObject(QObject* parent=0) : QObject(parent) {}
- void callEmitSignal() // 用於發送信號的函數
- {
- emit someSignal();
- }
- signals:
- void someSignal();
- };
然後再定義一個從QThread繼承的線程類SubThread,它包含一個SomeObject的對象指針obj,另外有一個slot函數someSolt,定義如下:
- class SubThread : public QThread
- {
- Q_OBJECT
- public:
- SubThread(QObject* parent=0) : QThread(parent){}
- virtual ~SubThread()
- {
- if (obj!=NULL) delete obj;
- }
- public slots:
- // slot function connected to obj's someSignal
- void someSlot();
- public:
- SomeObject * obj;
- };
- // slot function connected to obj's someSignal
- void SubThread::someSlot()
- {
- QString msg;
- msg.append(this->metaObject()->className());
- msg.append("::obj's thread is ");
- if (obj->thread() == qApp->thread())
- {
- msg.append("MAIN thread;");
- }
- else if (obj->thread() == this)
- {
- msg.append("SUB thread;");
- }
- else
- {
- msg.append("OTHER thread;");
- }
- msg.append(" someSlot executed in ");
- if (QThread::currentThread() == qApp->thread())
- {
- msg.append("MAIN thread;");
- }
- else if (QThread::currentThread() == this)
- {
- msg.append("SUB thread;");
- }
- else
- {
- msg.append("OTHER thread;");
- }
- qDebug() << msg;
- quit();
- }
這裏someSlot函數主要輸出了obj所在的線程和slot函數執行線程。
接着從SubThread又繼承了3個線程類,分別是SubThread1, SubThread2, SubThread3.分別實現線程的run函數。定義如下:
- class SubThread1 : public SubThread
- {
- Q_OBJECT
- public:
- SubThread1(QObject* parent=0);
- // reimplement run
- void run();
- };
- class SubThread2 : public SubThread
- {
- Q_OBJECT
- public:
- SubThread2(QObject* parent=0);
- // reimplement run
- void run();
- };
- class SubThread3 : public SubThread
- {
- Q_OBJECT
- public:
- SubThread3(QObject* parent=0);
- // reimplement run
- void run();
- };
在主程序中分別創建3個不同的線程並運行,查看運行結果。
- int main(int argc, char *argv[])
- {
- QCoreApplication a(argc, argv);
- SubThread1* t1 = new SubThread1(&a); //由主線程創建
- t1->start();
- SubThread2* t2 = new SubThread2(&a); //由主線程創建
- t2->start();
- SubThread3* t3 = new SubThread3(&a); //由主線程創建
- t3->start();
- return a.exec();
- }</span>
下面我們來分析不同寫法的程序,其obj對象所在的線程空間和someSlot函數執行的線程空間分別是怎樣的。
首先看SubThread1的實現:
- SubThread1::SubThread1(QObject* parent)
- : SubThread(parent)
- {
- obj = new SomeObject();//由主線程創建
- connect(obj, SIGNAL(someSignal()), this, SLOT(someSlot()));
- }
- // reimplement run
- void SubThread1::run()
- {
- obj->callEmitSignal();
- exec();
- }</span>
可以看到,obj是在構造函數中被創建的,那麼創建obj對象的線程也就是創建SubThread1的線程,一般是主線程,而不是SubThread1所代表的線程。同時由於obj和this(即t1)都位於主線程,所以someSlot函數也是由主線程來執行的。
而在線程SubThread2中,我們把obj對象的創建放到子線程的run函數中,那麼obj對象的線程就應該SubThread2代表的線程,即t2,就不再是主線程了。- SubThread2::SubThread2(QObject* parent)
- : SubThread(parent)
- {
- obj=0;
- }
- // reimplement run
- void SubThread2::run()
- {
- obj = new SomeObject(); //由當前子線程創建
- connect(obj, SIGNAL(someSignal()), this, SLOT(someSlot()));
- obj->callEmitSignal();
- exec();
- }</span>
同時,在connect函數中由於obj和this(這裏是t2)不是在同一個線程中,因此會採用QueuedConnection的方式,其slot函數由this對象所在的線程即主線程來執行。這裏有一個特別容易誤解的地方,就是這個slot函數雖然是子線程SubThread2的一個成員函數,connect操作也是在子線程內完成的,但是該函數的執行卻不在子線程內,而是在主線程內。
那麼如果想讓相應的slot函數在子線程內執行,該如何做呢?在子線程的run函數中創建obj對象的同時,在執行connect時指定連接方式爲DirectConnection,這樣就可以使slot函數在子線程中運行,因爲DirectConnection的方式始終由sender對象的線程執行。如- SubThread3::SubThread3(QObject* parent)
- : SubThread(parent)
- {
- obj=0;
- }
- // reimplement run
- void SubThread3::run()
- {
- obj = new SomeObject();
- connect(obj, SIGNAL(someSignal()), this, SLOT(someSlot()),
- Qt::DirectConnection);
- obj->callEmitSignal();
- exec();
- }
最後,該程序的運行結果應該是:
- "SubThread1::obj's thread is MAIN thread; someSlot executed in MAIN thread;"
- "SubThread2::obj's thread is SUB thread; someSlot executed in MAIN thread;"
- "SubThread3::obj's thread is SUB thread; someSlot executed in SUB thread;"
在這裏順便解釋下Qt::ConnectType這個枚舉:
signal/slot在底層會使用三種方式傳遞消息。參見QObject::connect()方法:
bool QObject::connect ( const QObject * sender, const char * signal, const QObject * receiver, const char * method, Qt::ConnectionType type = Qt::AutoCompatConnection )
最後一個參數是就是傳遞消息的方式了,有四個取值:
Qt::DirectConnection
When emitted, the signal is immediately delivered to the slot.
假設當前有4個slot連接到QPushButton::clicked(bool),當按鈕被按下時,QT就把這4個slot按連接的時間順序調用一遍。顯然這種方式不能跨線程(傳遞消息)。
Qt::QueuedConnection
When emitted, the signal is queued until the event loop is able to deliver it to the slot.
假設當前有4個slot連接到QPushButton::clicked(bool),當按鈕被按下時,QT就把這個signal包裝成一個 QEvent,放到消息隊列裏。QApplication::exec()或者線程的QThread::exec()會從消息隊列裏取消息,然後調用 signal關聯的幾個slot。這種方式既可以在線程內傳遞消息,也可以跨線程傳遞消息。
Qt::BlockingQueuedConnection
Same as QueuedConnection, except that the current thread blocks until the slot has been delivered. This connection type should only be used for receivers in a different thread. Note that misuse of this type can lead to dead locks in your application.
與Qt::QueuedConnection類似,但是會阻塞等到關聯的slot都被執行。這裏出現了阻塞這個詞,說明它是專門用來多線程間傳遞消息的。
Qt::AutoConnection
If the signal is emitted from the thread in which the receiving object lives, the slot is invoked directly, as with Qt::DirectConnection; otherwise the signal is queued, as with Qt::QueuedConnection.
這種連接類型根據signal和slot是否在同一個線程裏自動選擇Qt::DirectConnection或Qt::QueuedConnection
這樣看來,第一種類型的效率肯定比第二種高,畢竟第二種方式需要將消息存儲到隊列,而且可能會涉及到大對象的複製(考慮sig_produced(BigObject bo),bo需要複製到隊列裏)。
Qt::UniqueConnection
This is a flag that can be combined with any one of the above connection types, using a bitwise OR. When Qt::UniqueConnection is set, QObject::connect() will fail if the connection already exists (i.e. if the same signal is already connected to the same slot
for the same pair of objects).
這個枚舉我沒有用過,從翻譯上理解,和上面的枚舉或結合使用,而且相同的信號中只有一個信號與槽進行連接!
我還是不太理解,知道的可以留言!