为彻底搞清原因并解决问题,在查阅大量资料和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).
这个枚举我没有用过,从翻译上理解,和上面的枚举或结合使用,而且相同的信号中只有一个信号与槽进行连接!
我还是不太理解,知道的可以留言!