1. 程式人生 > >Qt 多執行緒程式設計中的物件執行緒與函式執行執行緒

Qt 多執行緒程式設計中的物件執行緒與函式執行執行緒

    為徹底搞清原因並解決問題,在查閱大量資料和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訊號。定義如下: class SomeObject : public QObject  
  1. {  
  2.     Q_OBJECT  
  3. public:  
  4.     SomeObject(QObject* parent=0) : QObject(parent) {}  
  5.     void callEmitSignal()  // 用於傳送訊號的函式
  6.     {  
  7.         emit someSignal();  
  8.     }  
  9. signals:  
  10.     void someSignal();  
  11. };  

然後再定義一個從QThread繼承的執行緒類SubThread,它包含一個SomeObject的物件指標obj,另外有一個slot函式someSolt,定義如下:

  1. class SubThread : public QThread  
  2. {  
  3.     Q_OBJECT  
  4. public:  
  5.     SubThread(QObject* parent=0) : QThread(parent){}  
  6.     virtual ~SubThread()  
  7.     {  
  8.         if (obj!=NULL) delete obj;  
  9.     }  
  10. public slots:  
  11.     // slot function connected to obj's someSignal
  12.     void someSlot();  
  13. public:  
  14.     SomeObject * obj;  
  15. };  
  16. // slot function connected to obj's someSignal
  17. void SubThread::someSlot()  
  18. {  
  19.     QString msg;  
  20.     msg.append(this->metaObject()->className());  
  21.     msg.append("::obj's thread is ");  
  22.     if (obj->thread() == qApp->thread())  
  23.     {  
  24.         msg.append("MAIN thread;");  
  25.     }  
  26.     elseif (obj->thread() == this)  
  27.     {  
  28.         msg.append("SUB thread;");  
  29.     }  
  30.     else
  31.     {  
  32.         msg.append("OTHER thread;");  
  33.     }  
  34.     msg.append(" someSlot executed in ");  
  35.     if (QThread::currentThread() == qApp->thread())  
  36.     {  
  37.         msg.append("MAIN thread;");  
  38.     }  
  39.     elseif (QThread::currentThread() == this)  
  40.     {  
  41.         msg.append("SUB thread;");  
  42.     }  
  43.     else
  44.     {  
  45.         msg.append("OTHER thread;");  
  46.     }  
  47.     qDebug() << msg;  
  48.     quit();  
  49. }

這裡someSlot函式主要輸出了obj所在的執行緒和slot函式執行執行緒。

    接著從SubThread又繼承了3個執行緒類,分別是SubThread1, SubThread2, SubThread3.分別實現執行緒的run函式。定義如下:

  1. class SubThread1 : public SubThread  
  2. {  
  3.     Q_OBJECT  
  4. public:  
  5.     SubThread1(QObject* parent=0);  
  6.     // reimplement run
  7.     void run();  
  8. };  
  9. class SubThread2 : public SubThread  
  10. {  
  11.     Q_OBJECT  
  12. public:  
  13.     SubThread2(QObject* parent=0);  
  14.     // reimplement run
  15.     void run();  
  16. };  
  17. class SubThread3 : public SubThread  
  18. {  
  19.     Q_OBJECT  
  20. public:  
  21.     SubThread3(QObject* parent=0);  
  22.     // reimplement run
  23.     void run();  
  24. };

    在主程式中分別建立3個不同的執行緒並執行,檢視執行結果。

  1. int main(int argc, char *argv[])  
  2. {  
  3.     QCoreApplication a(argc, argv);  
  4.     SubThread1* t1 = new SubThread1(&a); //由主執行緒建立
  5.     t1->start();  
  6.     SubThread2* t2 = new SubThread2(&a); //由主執行緒建立
  7.     t2->start();  
  8.     SubThread3* t3 = new SubThread3(&a); //由主執行緒建立
  9.     t3->start();  
  10.     return a.exec();  
  11. }</span>  

    下面我們來分析不同寫法的程式,其obj物件所在的執行緒空間和someSlot函式執行的執行緒空間分別是怎樣的。

    首先看SubThread1的實現:

  1. SubThread1::SubThread1(QObject* parent)  
  2.     : SubThread(parent)  
  3. {  
  4.     obj = new SomeObject();//由主執行緒建立
  5.     connect(obj, SIGNAL(someSignal()), this, SLOT(someSlot()));  
  6. }  
  7. // reimplement run
  8. void SubThread1::run()  
  9. {  
  10.     obj->callEmitSignal();  
  11.     exec();  
  12. }</span>  

可以看到,obj是在建構函式中被建立的,那麼建立obj物件的執行緒也就是建立SubThread1的執行緒,一般是主執行緒,而不是SubThread1所代表的執行緒。同時由於obj和this(即t1)都位於主執行緒,所以someSlot函式也是由主執行緒來執行的。

    而線上程SubThread2中,我們把obj物件的建立放到子執行緒的run函式中,那麼obj物件的執行緒就應該SubThread2代表的執行緒,即t2,就不再是主執行緒了。
  1. SubThread2::SubThread2(QObject* parent)  
  2.     : SubThread(parent)  
  3. {  
  4.     obj=0;  
  5. }  
  6. // reimplement run
  7. void SubThread2::run()  
  8. {  
  9.     obj = new SomeObject(); //由當前子執行緒建立
  10.     connect(obj, SIGNAL(someSignal()), this, SLOT(someSlot()));  
  11.     obj->callEmitSignal();  
  12.     exec();  
  13. }</span>  

同時,在connect函式中由於obj和this(這裡是t2)不是在同一個執行緒中,因此會採用QueuedConnection的方式,其slot函式由this物件所在的執行緒即主執行緒來執行。這裡有一個特別容易誤解的地方,就是這個slot函式雖然是子執行緒SubThread2的一個成員函式,connect操作也是在子執行緒內完成的,但是該函式的執行卻不在子執行緒內,而是在主執行緒內。

    那麼如果想讓相應的slot函式在子執行緒內執行,該如何做呢?在子執行緒的run函式中建立obj物件的同時,在執行connect時指定連線方式為DirectConnection,這樣就可以使slot函式在子執行緒中執行,因為DirectConnection的方式始終由sender物件的執行緒執行。如
  1. SubThread3::SubThread3(QObject* parent)  
  2.     : SubThread(parent)  
  3. {  
  4.     obj=0;  
  5. }  
  6. // reimplement run
  7. void SubThread3::run()  
  8. {  
  9.     obj = new SomeObject();  
  10.     connect(obj, SIGNAL(someSignal()), this, SLOT(someSlot()),  
  11.             Qt::DirectConnection);  
  12.     obj->callEmitSignal();  
  13.     exec();  
  14. }

    最後,該程式的執行結果應該是:

  1. "SubThread1::obj's thread is MAIN thread; someSlot executed in MAIN thread;"   
  2. "SubThread2::obj's thread is SUB thread; someSlot executed in MAIN thread;"   
  3. "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). 

這個列舉我沒有用過,從翻譯上理解,和上面的列舉或結合使用,而且相同的訊號中只有一個訊號與槽進行連線!

我還是不太理解,知道的可以留言!