计算机系统应用教程网站

网站首页 > 技术文章 正文

Qt推荐的多线程的理解 qt中多线程

btikc 2024-10-24 09:32:46 技术文章 8 ℃ 0 评论

目的

在Qt4.8之后,Qt多线程的写法最好还是通过QObject来实现,和线程的交互通过信号和槽(实际上其实是通过事件)联系。

用QObject来实现多线程有个非常好的优点,就是默认就支持事件循环(Qt的许多非GUI类也需要事件循环支持,如QTimer、QTcpSocket),QThread要支持事件循环需要在QThread::run()中调用QThread::exec()来提供对消息循环的支持,否则那些需要事件循环支持的类都不能正常发送信号,因此如果要使用信号和槽,那就直接使用QObject来实现多线程。

使用QObject创建多线程的方法如下:

写一个继承QObject的类,对需要进行复杂耗时逻辑的入口函数声明为槽函数

此类在旧线程new出来,不能给它设置任何父对象

同时声明一个QThread对象,在官方例子里,QThread并没有new出来,这样在析构时就需要调用QThread::wait(),如果是堆分配的话, 可以通过deleteLater来让线程自杀

把obj通过moveToThread方法转移到新线程中,此时object已经是在线程中了

把线程的finished信号和object的deleteLater槽连接,这个信号槽必须连接,否则会内存泄漏

正常连接其他信号和槽(在连接信号槽之前调用moveToThread,不需要处理connect的第五个参数,否则就显示声明用Qt::QueuedConnection来连接)

初始化完后调用'QThread::start()'来启动线程

在逻辑结束后,调用QThread::quit退出线程的事件循环

使用QObject来实现多线程比用继承QThread的方法更加灵活,整个类都是在新的线程中,通过信号槽和主线程传递数据。

情况

Qt有两种多线程的方法,其中一种是继承QThread的run函数,另外一种是把一个继承于QObject的类转移到一个Thread里。 Qt4.8之前都是使用继承QThread的run这种方法,但是Qt4.8之后,Qt官方建议使用第二种方法。两种方法区别不大,用起来都比较方便,但继承QObject的方法更加灵活。这里要记录的是如何正确的创建一个线程,特别是如何正确的退出一个线程。

一开始不理解其是怎么实现的,所以不太愿意用,理解其如何实现的,会发现这种用法,的确非常的好。

官网这样描述的:

QThread类提供了一种独立于平台的方式来管理线程。

QThread对象管理程序中的一个控制线程。QThreads在run()中开始执行。默认情况下,run()通过调用exec()启动事件循环,

并在线程内运行Qt事件循环。

您可以通过使用QObject::moveToThread()将工作对象移动到线程中来使用它们。

然后,Worker槽内的代码将在单独的线程中执行。然而,你可以自由地将Worker的插槽连接到任何信号,

来自任何对象,任何线程。由于一种称为排队连接的机制,跨不同线程连接信号和插槽是安全的。

例子如下:

cotroller的源码:

#ifndef CONTROLLER_H
#define CONTROLLER_H

#include <QObject>
#include <QThread>
#include "worker.h"

class Controller : public QObject
{
    Q_OBJECT
    QThread workerThread;
public:
    explicit Controller(QObject *parent = nullptr);
    ~Controller();
    void beginOperate();
public slots:
     void handleResults(const QString &str);
signals:
     void operate(const QString &);
};

#endif // CONTROLLER_H
#include "controller.h"

Controller::Controller(QObject *parent) : QObject(parent)
{
  Worker *worker = new Worker;
  worker->moveToThread(&workerThread);
  connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);
  connect(this, &Controller::operate, worker, &Worker::doWork);
  connect(worker, &Worker::resultReady, this, &Controller::handleResults);
  workerThread.start();
}

Controller::~Controller()
{
  workerThread.quit();
  workerThread.wait();
}

void Controller::beginOperate()
{
    QString str = "begin";
    emit operate(str);
}

void Controller::handleResults(const QString &str)
{
   qDebug("enter function Controller::handleResults str=%s", str.toStdString().c_str());
   QThread *currentThread = QThread::currentThread();
   qDebug("exit function Controller::handleResults currentThread=%p", currentThread);
}

worker的源码:

#ifndef WORKER_H
#define WORKER_H

#include <QObject>

class Worker : public QObject
{
    Q_OBJECT
public:
    explicit Worker(QObject *parent = nullptr);

signals:
    void resultReady(const QString &result);

public slots:
    void doWork(const QString ?meter);
};

#endif // WORKER_H
#include "worker.h"
#include <QThread>

Worker::Worker(QObject *parent) : QObject(parent)
{

}

void Worker::doWork(const QString ?meter)
{
 qDebug("enter function Worker::doWork parameter=%s", parameter.toStdString().c_str());
 QString result;
 /* ... here is the expensive or blocking operation ... */
 result = "executing";
 emit resultReady(result);
 QThread *currentThread = QThread::currentThread();
 qDebug("exit function Worker::doWork currentThread=%p", currentThread);
}

main函数:

#include <QCoreApplication>
#include "controller.h"
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    Controller *controller = new Controller();
    controller->beginOperate();
    return a.exec();
}

执行情况:


可以见得,是运行在不同的线程内,


总结

其理解的关键就是在于thread默认运行着exec(),时刻监听着事件队列的情况,如果队列里有事件,就取出来执行;

有了这一个事件队列,可比原来直接在run()当中执行方便多了,这样也理解了moveToThread方法的含义,其含义就是把整个对象放到这一个线程里,这个线程通过exec()监听着事件队列,如果事件队列有事件,就取出来执行,非常的方便,当然也可以放一个QTimer控件在线程里,进行定时执行,也是可以的。

最后用一图进行总结:


Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表