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信号。定义如下:

[cpp] view plain copy

  1. <span style="font-family: SimSun; font-size: 16px; ">// define Object class</span>  

[cpp] view plain copy

  1. <span style="font-family:SimSun;font-size:16px;">class SomeObject : public QObject  

  2. {  

  3.     Q_OBJECT  

  4. public:  

  5.     SomeObject(QObject* parent=0) : QObject(parent) {}  

  6.     void callEmitSignal()  // 用于发送信号的函数  

  7.     {  

  8.         emit someSignal();  

  9.     }  

  10. signals:  

  11.     void someSignal();  

  12. };  

  13. </span>  

然后再定义一个从QThread继承的线程类SubThread,它包含一个SomeObject的对象指针obj,另外有一个slot函数someSolt,定义如下:

[cpp] view plain copy

  1. <span style="font-family:SimSun;font-size:16px;">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.     else if (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.     else if (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. }</span>  

这里someSlot函数主要输出了obj所在的线程和slot函数执行线程。

    接着从SubThread又继承了3个线程类,分别是SubThread1, SubThread2, SubThread3.分别实现线程的run函数。定义如下:

[cpp] view plain copy

  1. <span style="font-family:SimSun;font-size:16px;">// define sub thread class 1  

  2. class SubThread1 : public SubThread  

  3. {  

  4.     Q_OBJECT  

  5. public:  

  6.     SubThread1(QObject* parent=0);  

  7.     // reimplement run  

  8.     void run();  

  9. };  

  10. class SubThread2 : public SubThread  

  11. {  

  12.     Q_OBJECT  

  13. public:  

  14.     SubThread2(QObject* parent=0);  

  15.     // reimplement run  

  16.     void run();  

  17. };  

  18. class SubThread3 : public SubThread  

  19. {  

  20.     Q_OBJECT  

  21. public:  

  22.     SubThread3(QObject* parent=0);  

  23.     // reimplement run  

  24.     void run();  

  25. };</span>  

    在主程序中分别创建3个不同的线程并运行,查看运行结果。

[cpp] view plain copy

  1. <span style="font-family:SimSun;font-size:16px;">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的实现:

[cpp] view plain copy

  1. <span style="font-family:SimSun;font-size:16px;">////////////////////////////////////////////////////////  

  2. // class SubThread1  

  3. ////////////////////////////////////////////////////////  

  4. SubThread1::SubThread1(QObject* parent)  

  5.     : SubThread(parent)  

  6. {  

  7.     obj = new SomeObject();//由主线程创建  

  8.     connect(obj, SIGNAL(someSignal()), this, SLOT(someSlot()));  

  9. }  

  10. // reimplement run  

  11. void SubThread1::run()  

  12. {  

  13.     obj->callEmitSignal();  

  14.     exec();  

  15. }</span>  

可以看到,obj是在构造函数中被创建的,那么创建obj对象的线程也就是创建SubThread1的线程,一般是主线程,而不是SubThread1所代表的线程。同时由于obj和this(即t1)都位于主线程,所以someSlot函数也是由主线程来执行的。

    而在线程SubThread2中,我们把obj对象的创建放到子线程的run函数中,那么obj对象的线程就应该SubThread2代表的线程,即t2,就不再是主线程了。

[cpp] view plain copy

  1. <span style="font-family:SimSun;font-size:16px;">////////////////////////////////////////////////////////  

  2. // class SubThread2  

  3. ////////////////////////////////////////////////////////  

  4. SubThread2::SubThread2(QObject* parent)  

  5.     : SubThread(parent)  

  6. {  

  7.     obj=0;  

  8. }  

  9. // reimplement run  

  10. void SubThread2::run()  

  11. {  

  12.     obj = new SomeObject(); //由当前子线程创建  

  13.     connect(obj, SIGNAL(someSignal()), this, SLOT(someSlot()));  

  14.     obj->callEmitSignal();  

  15.     exec();  

  16. }</span>  

同时,在connect函数中由于obj和this(这里是t2)不是在同一个线程中,因此会采用QueuedConnection的方式,其slot函数由this对象所在的线程即主线程来执行。这里有一个特别容易误解的地方,就是这个slot函数虽然是子线程SubThread2的一个成员函数,connect操作也是在子线程内完成的,但是该函数的执行却不在子线程内,而是在主线程内。

    那么如果想让相应的slot函数在子线程内执行,该如何做呢?在子线程的run函数中创建obj对象的同时,在执行connect时指定连接方式为DirectConnection,这样就可以使slot函数在子线程中运行,因为DirectConnection的方式始终由sender对象的线程执行。如

[cpp] view plain copy

  1. <span style="font-family:SimSun;font-size:16px;">////////////////////////////////////////////////////////  

  2. // class SubThread3  

  3. ////////////////////////////////////////////////////////  

  4. SubThread3::SubThread3(QObject* parent)  

  5.     : SubThread(parent)  

  6. {  

  7.     obj=0;  

  8. }  

  9. // reimplement run  

  10. void SubThread3::run()  

  11. {  

  12.     obj = new SomeObject();  

  13.     connect(obj, SIGNAL(someSignal()), this, SLOT(someSlot()),  

  14.             Qt::DirectConnection);  

  15.     obj->callEmitSignal();  

  16.     exec();  

  17. }</span>  

    最后,该程序的运行结果应该是:

[plain] view plain copy

  1. <span style="font-family:SimSun;font-size:16px;">"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;" </span>  


你可能感兴趣的:(Qt多线程编程中的对象线程与函数执行线程)