首先这篇文章我也是东凑西拼,所以确实有些人会觉得很乱。但是在这个到处拼凑别人博客的时候,解决了我几个疑惑,所以大家可以带着这几个问题看这篇博客。
我的疑惑1:辨别对象所依赖的线程;辨别run()函数、槽函数是在主线程中执行还是在次线程中执行?
我的疑惑2:如何区分直接连接、队列连接、自动连接?
1. 线程的依附性
一个QObject实例是具有线程依附性的,或者说它驻留在某个线程。当一个OQbject接收到队列信号(queued signal)或者投递事件(posted event),槽函数或者事件处理函数运行在对象驻留的线程中(这句话尤为重要)。
一个QObject默认是位于创建它的线程内。一个对象的线程依附性可以使用thread()查询,并且可以通过moveToThread()改变。
所有的QObjects必须和它们的父类位于同一个线程。所以:
- 1)如果牵涉到的两个QObject对象位于不同的线程,setParent()会失败。
- 2)当一个QObject对象移动到另一个线程,它的所有子类也会自动移动。
- 3)如果QObject对象有父类,moveToThread()会失败。
- 4)如果QObject对象在QThread::run()函数内创建,它们不可以成为QThread对象的子类,因为QThread并不是驻留在调用QThread::run()的线程内。
注意:一个QObject的成员变量不会自动成为它的子类。父与子关系必须通过传递指针给子类的构造函数,或者调用setParent()函数来设置。没有这一步,当moveToThread()被调用时,对象的成员变量仍旧会留在旧的线程中。
2. QThread::run
run 对于线程的作用相当于main函数对于应用程序。它是线程的入口,run的开始和结束意味着线程的开始和结束。可能大家不知道我为什么要说这句话的意思,就直接看下一段代码。
class Thread:public QThread
{
Q_OBJECT
public:
Thread(QObject* parent=0):QThread(parent){}
public slots:
void slot() { ... }
signals:
void sig();
protected:
void run() { ...}
};
int main(int argc, char** argv)
{
...
Thread thread;
...
}
由以上的话可以知道,run函数一定是在次线程中运行的。那么对于slot函数来说是在主线程中运行的还是在次线程中运行的呢?继续往下看。
3. QObject::connect
关于这个函数我们现在只说他的最后一个参数,Qt支持5种信号-槽连接类型,这里只说常用的三个连接方式。
自动连接(Auto Connection)
- 这是默认设置
- 如果信号在接收者所依附的线程内发射,则等同于直接连接
- 如果发射信号的线程和接受者所依附的线程不同,则等同于队列连接
直接连接
- 当信号发射时,槽函数将直接被调用。
- 无论槽函数所属对象在哪个线程,槽函数都在发射信号的线程内执行。
队列连接(Queued Connection)
- 当控制权回到接受者所依附线程的事件循环时,槽函数被调用。
- 槽函数在接收者所依附线程执行。
那么继续上面的提问,slot函数到底是在次线程中执行的还是在主线程中执行的呢?
这里强调两个概念:发送信号的线程 和 接收者所依附的线程。而 slot 函数属于我们在main中创建的对象 thread,即thread依附于主线程。这样就能说明slot是在主线程中执行的。如果还是看不懂继续向下看。。
4. 管理的线程和依附的线程的区别
QThread 是用来管理线程的,它所依附的线程和它管理的线程并不是同一个东西.
QThread 所依附的线程,就是执行 QThread t(0) 或 QThread * t=new QThread(0) 的线程。也就是咱们这儿的主线程.
QThread 管理的线程,就是 run 启动的线程。也就是次线程
因为QThread的对象依附在主线程中,所以他的slot函数会在主线程中执行,而不是次线程。除非:
- QThread 对象依附到次线程中(通过movetoThread)
- slot 和信号是直接连接,且信号在次线程中发射
5. 判断连接方式
上面说了那么多,咱们来几个练习 ,先来判断下下面的程序都有什么运行结果。
示例一
myobject.h
#ifndef MYOBJECT_H
#define MYOBJECT_H
#include <QObject>
class MyObject : public QObject
{
Q_OBJECT
public:
explicit MyObject(QObject *parent = 0);
public slots:
void start();
};
#endif // MYOBJECT_H
myobject.cpp
#include "MyObject.h"
#include <QDebug>
#include <QThread>
MyObject::MyObject(QObject *parent)
: QObject(parent)
{
}
void MyObject::start()
{
qDebug() << QString("my object thread id:") << QThread::currentThreadId();
}
main.cpp
#include "MyObject.h"
#include <Qtcore/QCoreApplication>
#include <QDebug>
#include <QThread>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
qDebug()<< QString("main thread id:") << QThread::currentThreadId();
MyObject object;
QThread thread;
object.moveToThread(&thread);//把object依附到次线程中,也就是Thread线程中
QObject::connect(&thread, &QThread::started, &object, &MyObject::start);
thread.start();
return a.exec();
}
运行结果:
main thread id:" 0x3478
my object thread id:" 0x4e0c
显然主线程与槽函数的线程是不同的。因为moveToThread后MyObject所在的线程为QThread,继上面介绍的thread.start()执行后首先会发射started()信号,也就是说started()信号发射是在次线程中进行的,所以无论采取Qt::AutoConnection、Qt::DirectConnection、Qt::QueuedConnection哪种连接方式,主线程与槽函数的线程都是不同的。
**
示例二:对上面代码进行如下修改**
MyObject object;
QThread thread;
// object.moveToThread(&thread);//把object依附到次线程中,也就是Thread线程中
QObject::connect(&thread, &QThread::started, &object, &MyObject::start,Qt::DirectConnection);
thread.start();
运行结果如下:
main thread id:" 0x2eec
my object thread id:" 0x51ac
显然主线程与槽函数的线程是不同的,MyObject所依附的线程为主线程(因为注释掉了moveToThread),继上面介绍的Qt::DirectConnection(无论槽函数所属对象在哪个线程,槽函数都在发射信号的线程内执行)。也就是说started()信号发射是在次线程中进行的,槽函数也是在次线程中进行的,所以主线程与槽函数的线程是不同的。
示例三:对上面代码进行如下修改
MyObject object;
QThread thread;
// object.moveToThread(&thread);//把object依附到次线程中,也就是Thread线程中
QObject::connect(&thread, &QThread::started, &object, &MyObject::start,Qt::QueuedConnection);
thread.start();
运行结果如下:
"main thread id:" 0x3418
"my object thread id:" 0x3418
显然主线程与槽函数的线程是相同的,继上面介绍的Qt::QueuedConnection(槽函数在接收者所依附线程执行)。也就是说started()信号发射是在次线程中进行的,但MyObject所依附的线程为主线程(因为注释掉了moveToThread),所以主线程与槽函数的线程必然是相同的。
示例四:对上面的代码做出如下修改
MyObject object;
QThread thread;
// object.moveToThread(&thread);//把object依附到次线程中,也就是Thread线程中
QObject::connect(&thread, &QThread::started, &object, &MyObject::start,Qt::AutoConnection);
thread.start();
运行结果如下:
"main thread id:" 0x2c3c
"my object thread id:" 0x2c3c
显然主线程与槽函数的线程是相同的,MyObject所依附的线程为主线程(因为注释掉了moveToThread),继上面介绍的Qt::AutoConnection(如果信号在接收者所依附的线程内发射,则等同于直接连接。如果发射信号的线程和接受者所依附的线程不同,则等同于队列连接。)。因为started()信号和MyObject依附的线程不同,所以结果和Qt::QueuedConnection对应的相同,所以主线程与槽函数的线程是相同的。
6. 总结
说了这么多,其实就是想告诉你,上一篇博客讲的第二种方法非常的好。也就是我们在使用线程的时候,就可以将一个类派生自QObject,然后实现所有的signal/slot,然后通过调用movetothread函数来使他们执行在新的线程里面,而不是每次都要重新派生QThread,并且派生QThread函数的另外一个不好的地方是只有run函数内部的代码才会执行在新线程里面,相比起来,派生QObject并使用movetothread函数更具有灵活性。
参考博客(都是从这些博客搬过来的):
Qt 的线程与事件循环
线程依附性
Qt之线程(QThread)
示例代码:
链接:https://pan.baidu.com/s/16npK5NOOlVVAnP20yx_CyA 密码:xus1