QObject线程依附性以及QT多线程

Part1:对象的依附线程

QT参考文档(Qt 5.12.3 Reference Documentation)中讲述QtCore 模块中QObject时(qthelp://org.qt-project.qtcore.5123/qtcore/qtcore-module.html)讲到了QObject的线程依附性,原文如下:

Thread Affinity

A QObject instance is said to have a thread affinity, or that it lives in a certain thread. When a QObject receives a queued signal or a posted event, the slot or event handler will run in the thread that the object lives in.

Note: If a QObject has no thread affinity (that is, if thread() returns zero), or if it lives in a thread that has no running event loop, then it cannot receive queued signals or posted events.

By default, a QObject lives in the thread in which it is created. An object's thread affinity can be queried using thread() and changed using moveToThread().

All QObjects must live in the same thread as their parent. Consequently:

  • setParent() will fail if the two QObjects involved live in different threads.
  • When a QObject is moved to another thread, all its children will be automatically moved too.
  • moveToThread() will fail if the QObject has a parent.
  • If QObjects are created within QThread::run(), they cannot become children of the QThread object because the QThread does not live in the thread that calls QThread::run().

Note: A QObject's member variables do not automatically become its children. The parent-child relationship must be set by either passing a pointer to the child's constructor, or by calling setParent(). Without this step, the object's member variables will remain in the old thread when moveToThread() is called.

译文如下:

一个QObject实例是具有线程依附性的,或者说它驻留在某个线程。当一个OQbject接收到队列信号(queued signal)或者投递事件(posted event),槽函数或者事件处理函数运行在对象驻留的线程中。

    注意:如果一个QObject没有线程依附性(也就是说thread()函数返回0),或者它位于没有运行事件循环的线程内,那么它就不能接收到队列信号或者投递事件。

    一个QObject默认是位于创建它的线程内。一个对象的线程依附性可以使用thread()查询,并且可以通过moveToThread()改变。

所有的QObjects必须和它们的父类位于同一个线程。所以:

    1)如果牵涉到的两个QObject对象位于不同的线程,setParent()会失败。

    2)当一个QObject对象移动到另一个线程,它的所有子类也会自动移动。

    3)如果QObject对象有父类,moveToThread()会失败。

    4)如果QObject对象在QThread::run()函数内创建,它们不可以成为QThread对象的子对象,因为QThread并不是驻留在调用QThread::run()的线程内。

 注意:一个QObject的成员变量不会自动成为它的子对象。父—子关系必须通过传递指针给子类的构造函数,或者调用setParent()函数来设置。没有这一步,当moveToThread()被调用时,对象的成员变量仍旧会留在旧的线程中。

上代码演示:

/* threadaffinity.h */
#ifndef THREADAFFINITY
#define THREADAFFINITY
 
#include <QThread>
 
class Thread : public QThread
{
    Q_OBJECT
 
signals:
    void sendSignal();
protected:
    void run();
};
 
class Object : public QObject
{
    Q_OBJECT
 
public slots:
    void receiveSlot();
};
 
 
#endif // THREADAFFINITY
/* threadaffinity.cpp */
#include <QDebug>
#include <QTimer>
#include "threadaffinity.h"

void Object::receiveSlot()
{
    QTimer* timer = new QTimer;
    qDebug() << "Object::receiveSlot " << timer->thread();
    delete timer;
}

void Thread::run()
{
    QTimer* timer = new QTimer;
    qDebug() << "Thread::run " << timer->thread();
    emit sendSignal();
    delete timer;
}
/* main.cpp */
#include "threadaffinity.h"
#include <Qtcore/QCoreApplication>
#include <QDebug>

int main(int argc, char* argv[])
{
    QCoreApplication app(argc, argv);
    Thread thread;
    Object object;

    qDebug() << "main " << app.thread();
    qDebug() << "thread " << thread.thread();
    qDebug() << "object " << object.thread();

    QObject::connect(&thread, SIGNAL(sendSignal()), &object, SLOT(receiveSlot()));
    thread.start();

    return app.exec();
}

结果如下,看Object类的槽函数所依附的线程id

图

  Part2 QT多线程开发

对于QT的多线程开发有很多,高级API如QtConcurrent以及常规的方法使用QThread,

对于QThread的使用方法有两种:

  • QObject::moveToThread()
  • 继承QThread类

1.QObject::moveToThread
方法描述:

定义一个继承于QObject的worker类,在worker类中定义一个槽slot函数doWork(),这个函数中定义线程需要做的工作;
在要使用线程的controller类中,新建一个QThread的对象和woker类对象,使用moveToThread()方法将worker对象的事件循环全部交由QThread对象处理;
建立相关的信号函数和槽函数进行连接,然后发出信号触发QThread的槽函数,使其执行工作。
 

#ifndef WORKER_H
#define WORKER_H
#include <QObject>
#include<QDebug>
#include<QThread>
class Worker:public QObject                    //work定义了线程要执行的工作
{
    Q_OBJECT
public:
    Worker(QObject* parent = nullptr){}
public slots:
    void doWork(int parameter)                        //doWork定义了线程要执行的操作
    {
        qDebug()<<"receive the execute signal---------------------------------";
        qDebug()<<"     current thread ID:"<<QThread::currentThreadId();
       for(int i = 0;i!=1000000;++i)
       {
        ++parameter;
       }
       qDebug()<<"      finish the work and sent the resultReady signal\n";
       emit resultReady(parameter);           //emit啥事也不干,是给程序员看的,表示发出信号发出信号
    }
 
signals:
    void resultReady(const int result);               //线程完成工作时发送的信号
};
 
#endif // WORKER_H
#ifndef CONTROLLER_H
#define CONTROLLER_H
#include <QObject>
#include<QThread>
#include<QDebug>
class Controller : public QObject            //controller用于启动线程和处理线程执行结果
{
    Q_OBJECT
    QThread workerThread;
public:
    Controller(QObject *parent= nullptr);
    ~Controller();
public slots:
    void handleResults(const int rslt)                        //处理线程执行的结果
    {
        qDebug()<<"receive the resultReady signal---------------------------------";
        qDebug()<<"     current thread ID:"<<QThread::currentThreadId()<<'\n';
        qDebug()<<"     the last result is:"<<rslt;
    }
signals:
    void operate(const int);                        //发送信号触发线程
};
 
#endif // CONTROLLER_H
#include "controller.h"
#include <worker.h>
Controller::Controller(QObject *parent) : QObject(parent)
{
    Worker *worker = new Worker;
    worker->moveToThread(&workerThread);            //调用moveToThread将该任务交给workThread
 
    connect(this, SIGNAL(operate(const int)), worker, SLOT(doWork(int)));            //operate信号发射后启动线程工作
    connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);            //该线程结束时销毁
    connect(worker, SIGNAL(resultReady(int)), this, SLOT(handleResults(int)));            //线程结束后发送信号,对结果进行处理
 
    workerThread.start();                //启动线程
    qDebug()<<"emit the signal to execute!---------------------------------";
    qDebug()<<"     current thread ID:"<<QThread::currentThreadId()<<'\n';
    emit operate(0);
}
 
Controller::~Controller()        //析构函数中调用quit()函数结束线程
{
    workerThread.quit();
    workerThread.wait();
}

2.继承QThread类

方法描述

  • 自定义一个继承QThread的类MyThread,重载MyThread中的run()函数,在run()函数中写入需要执行的工作;
  • 调用start()函数来启动线程。
#ifndef MYTHREAD_H
#define MYTHREAD_H
#include<QThread>
#include<QDebug>
class MyThread : public QThread
{
    Q_OBJECT
public:
    MyThread(QObject* parent = nullptr);
signals:                //自定义发送的信号
    void myThreadSignal(const int);
public slots:                //自定义槽
    void myThreadSlot(const int);
protected:
    void run() override;
};
 
#endif // MYTHREAD_H
#include "mythread.h"
 
MyThread::MyThread(QObject *parent)
{
 
}
 
void MyThread::run()
{
    qDebug()<<"myThread run() start to execute";
    qDebug()<<"     current thread ID:"<<QThread::currentThreadId()<<'\n';
    int count = 0;
    for(int i = 0;i!=1000000;++i)
    {
     ++count;
    }
    emit myThreadSignal(count);
    exec();
}
 
void MyThread::myThreadSlot(const int val)
{
    qDebug()<<"myThreadSlot() start to execute";
    qDebug()<<"     current thread ID:"<<QThread::currentThreadId()<<'\n';
    int count = 888;
    for(int i = 0;i!=1000000;++i)
    {
     ++count;
    }
}
#include "controller.h"
#include <mythread.h>
Controller::Controller(QObject *parent) : QObject(parent)
{
    myThrd = new MyThread;
    connect(myThrd,&MyThread::myThreadSignal,this,&Controller::handleResults);
    connect(myThrd, &QThread::finished, this, &QObject::deleteLater);            //该线程结束时销毁
    connect(this,&Controller::operate,myThrd,&MyThread::myThreadSlot);
 
    myThrd->start();
    QThread::sleep(5);
    emit operate(999);
}
 
Controller::~Controller()
{
    myThrd->quit();
    myThrd->wait();
}

两种方法的比较

两种方法来执行线程都可以,随便你的喜欢。不过看起来第二种更加简单,容易让人理解。不过我们的兴趣在于这两种使用方法到底有什么区别?其最大的区别在于:

moveToThread方法,是把我们需要的工作全部封装在一个类中,将每个任务定义为一个的槽函数,再建立触发这些槽的信号,然后把信号和槽连接起来,最后将这个类调用moveToThread方法交给一个QThread对象,再调用QThread的start()函数使其全权处理事件循环。于是,任何时候我们需要让线程执行某个任务,只需要发出对应的信号就可以。其优点是我们可以在一个worker类中定义很多个需要做的工作,然后发出触发的信号线程就可以执行。相比于子类化的QThread只能执行run()函数中的任务,moveToThread的方法中一个线程可以做很多不同的工作(只要发出任务的对应的信号即可)。 
子类化QThread的方法,就是重写了QThread中的run()函数,在run()函数中定义了需要的工作。这样的结果是,我们自定义的子线程调用start()函数后,便开始执行run()函数。如果在自定义的线程类中定义相关槽函数,那么这些槽函数不会由子类化的QThread自身事件循环所执行,而是由该子线程的拥有者所在线程(一般都是主线程)来执行。子类化的QThread只能执行run()函数中的任务直到run()函数退出,而它的槽函数根本不会被自己的线程执行。

其实,对于多线程开发,QT给用户封装了很多很好的架构,后续再分享!
 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值