1. 程式人生 > 實用技巧 >多執行緒中的訊號與槽(中)

多執行緒中的訊號與槽(中)

令人不解的問題:

當槽函式是執行緒類的成員時,為什麼依然不在本執行緒內被呼叫執行?

隱藏的問題:
物件依附於哪一個執行緒?
物件的依附性與槽函式執行的關係?
物件的依附性是否可以改變?

物件依附於哪個執行緒?
預設情況下,物件依附於自身被建立的執行緒
例如:物件在主執行緒(main()函式)中被建立,則依附於主執行緒

int main(int argc, char* argv[])
{
   //...
   TestThread t;     //依附於主執行緒
   MyObject m;       //依附於主執行緒
}

物件的依附性與槽函式執行的關係?
預設情況下,槽函式在其所依附的執行緒中被呼叫執行

int
main(int argc, char* argv[]) { //... TestThread t; //依附於主執行緒 MyObject m; //依附於主執行緒 //下面連線中的槽函式都在主執行緒中被呼叫執行 QObject::connect(&t,SIGNAL(started()),&m,SLOT(getStarted())); QObject::connect(&t,SIGNAL(testSignal()),&m,SLOT(testSlot())); }

物件的依附性是否可以改變?
QObject::moveToThread用於改變物件的執行緒的依附性,使得物件的槽函式在依附的執行緒中被呼叫執行

int main(int argc, char* argv[])
{
   //...
   TestThread t;     //依附於主執行緒
   MyObject m;       //依附於主執行緒
   
   //改變物件m的執行緒的依附性,使其依附於執行緒t
   m.moveToThread(&t)
}

物件的依附性

MyObject.h

#ifndef MYOBJECT_H
#define MYOBJECT_H
#include <QObject>


class MyObject : public QObject
{
    Q_OBJECT
public
: MyObject(); protected slots: void getStarted(); void testSlot(); }; #endif // MYOBJECT_H
View Code

TestThread.h

#ifndef TESTTHREAD_H
#define TESTTHREAD_H

#include <QThread>

class TestThread : public QThread
{
    Q_OBJECT
protected:
    void run();
public:
    TestThread();

signals:
    void testSignal();
protected slots:
    void testSlot();
};

#endif // TESTTHREAD_H
View Code

MyObject.cpp

#include "MyObject.h"
#include <QObject>
#include <QThread>
#include <QDebug>

MyObject::MyObject()
{

}

void MyObject::getStarted()
{
    qDebug() <<"void MyObject::getStarted()tid = " << QThread::currentThreadId() ;
}

void MyObject::testSlot()
{
    qDebug() << "void MyObject::testSlot() " << QThread::currentThreadId() ;
}
View Code

TestThread.cpp

#include "TestThread.h"
#include <QDebug>

TestThread::TestThread()
{
    connect(this,SIGNAL(testSignal()),this,SLOT(testSlot()));
}

void TestThread::run()
{
   qDebug() << "void TestThread::run() begin...tid = " << currentThreadId();

   for(int i=0; i<10; i++)
   {
       qDebug() << "void TestThread::run() i = " << i;
       sleep(1);
   }

   emit testSignal(); //發射的訊號誰來接收呢,可以在建構函式中將訊號和槽函式進行關聯。

   qDebug() << "void TestThread::run() end...";
}

void TestThread::testSlot()
{
    qDebug() << "void TestThread::testSlot() tid = " << currentThreadId();
}
View Code

main.cpp

#include <QCoreApplication>
#include <QThread>
#include <QDebug>
#include "TestThread.h"
#include "MyObject.h"


int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    qDebug() << "main tid() " << QThread::currentThreadId();

    TestThread t;
    MyObject m;

    QObject::connect(&t,SIGNAL(started()),&m,SLOT(getStarted()));
    QObject::connect(&t,SIGNAL(testSignal()),&m,SLOT(testSlot()));

    m.moveToThread(&t);

    t.start();
    return a.exec();
}
View Code

課程中使用的是Qt4,MyObject相關的槽函式沒有被呼叫,但是使用Qt5.4,MyObject相關的槽函式被呼叫了。

下面分析沒有被呼叫的情況:

問題:
實驗中物件m的槽函式為什麼沒有全部被執行?

執行緒中的事件迴圈
訊號與槽的機制需要事件迴圈的支援
QThread類中提供的exec()函式用於開啟執行緒的事件迴圈
只有開啟事件迴圈,槽函式才能在訊號傳送後被呼叫

訊號的傳送是隨時隨地都可以完成的,傳送完成後,訊號就到事件佇列中去了。訊號進入事件佇列中去有什麼用呢?
沒人理它,它什麼用都沒有。如何處理事件佇列呢?此時事件迴圈就派上用場了。
但凡通過exec開啟了事件迴圈,就會不停的從事件佇列中取訊號,取到訊號後就會去判斷該訊號有沒有關聯相關的槽函式,
如果有對應的槽函式,則呼叫相應的槽函式。

想要槽函式在指定的執行緒中被呼叫,需要在指定的執行緒中呼叫exec函式,開啟事件迴圈。
小結論:
前提條件
物件依附的執行緒開啟了事件迴圈
後置結果
物件中的槽函式在依附的執行緒中被呼叫執行

只需要在TestThread.cpp的run函式中加上exec函式即可

void TestThread::run()
{
   qDebug() << "void TestThread::run() begin...tid = " << currentThreadId();

   for(int i=0; i<10; i++)
   {
       qDebug() << "void TestThread::run() i = " << i;
       sleep(1);
   }

   emit testSignal(); //發射的訊號誰來接收呢,可以在建構函式中將訊號和槽函式進行關聯。

   exec();
   qDebug() << "void TestThread::run() end...";
}
View Code

從列印結果看,與上面沒有使用exec的執行結果並無不同,很可能是因為版本不同造成的。

從列印結果看,MyObject的兩個槽函式都被呼叫了,且是在依附於t的那個執行緒。但是t的槽函式還是依附於主執行緒,我也想讓t的槽函式依附t,怎麼操作?非常簡單,只需要在main.cpp中的main函式中,加入t.moveToThread(&t)即可,如下所示:

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    qDebug() << "main tid() " << QThread::currentThreadId();

    TestThread t;
    MyObject m;

    QObject::connect(&t,SIGNAL(started()),&m,SLOT(getStarted()));
    QObject::connect(&t,SIGNAL(testSignal()),&m,SLOT(testSlot()));

    m.moveToThread(&t);
    t.moveToThread(&t);

    t.start();
    return a.exec();
}
View Code

研究槽函式的具體執行執行緒有什麼意義?
當訊號的傳送與對應槽函式的執行在不同執行緒中,可能產生臨界資源的競爭問題

比如說,在run函式對某一個臨界資源進行修改,在槽函式中也對臨界資源進行修改,槽函式的呼叫是在另一個執行緒中完成的,此時呼叫槽函式的執行緒和本身的執行緒就可能產生競爭問題。