qt多线程的一些细节

首先这篇文章我也是东凑西拼,所以确实有些人会觉得很乱。但是在这个到处拼凑别人博客的时候,解决了我几个疑惑,所以大家可以带着这几个问题看这篇博客。
我的疑惑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种信号-槽连接类型,这里只说常用的三个连接方式。

  1. 自动连接(Auto Connection)

    • 这是默认设置
    • 如果信号在接收者所依附的线程内发射,则等同于直接连接
    • 如果发射信号的线程和接受者所依附的线程不同,则等同于队列连接
  2. 直接连接

    • 当信号发射时,槽函数将直接被调用。
    • 无论槽函数所属对象在哪个线程,槽函数都在发射信号的线程内执行。
  3. 队列连接(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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值