Qt多線程編程中的對象線程與函數執行線程


  近來用Qt編寫一段多線程的TcpSocket通信程序,被其中Qt中報的幾個warning搞暈了,一會兒是說“Cannot create children for a parent that is in a different thread”,有時候又是“QSocketNotifier: socket notifiers cannot be enabled from another thread”,還經常又Assert failure:Cannot send events toobjects owned by a different thread,從而導致程序崩潰。

    為徹底搞清原因並解決問題,在查閱大量資料和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  
{  
    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函數。定義如下:

// define sub thread class 1  
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();  
}

 

    下面我們來分析不同寫法的程序,其obj對象所在的線程空間和someSlot函數執行的線程空間分別是怎樣的。

    首先看SubThread1的實現:

////////////////////////////////////////////////////////  
// class 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();  
}

 

  可以看到,obj是在構造函數中被創建的,那么創建obj對象的線程也就是創建SubThread1的線程,一般是主線程,而不是SubThread1所代表的線程。同時由於obj和this(即t1)都位於主線程,所以someSlot函數也是由主線程來執行的。

   而在線程SubThread2中,我們把obj對象的創建放到子線程的run函數中,那么obj對象的線程就應該SubThread2代表的線程,即t2,就不再是主線程了。

////////////////////////////////////////////////////////  
// class SubThread2  
////////////////////////////////////////////////////////  
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();  
}

 

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

  那么如果想讓相應的slot函數在子線程內執行,該如何做呢?在子線程的run函數中創建obj對象的同時,在執行connect時指定連接方式為DirectConnection,這樣就可以使slot函數在子線程中運行,因為DirectConnection的方式始終由sender對象的線程執行。如

////////////////////////////////////////////////////////  
// class SubThread3  
////////////////////////////////////////////////////////  
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;" 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM