本文介绍Qt多线程编程-run()方法。
Qt多线程编程主要有2种方法,前面已经介绍了moveToThread()方法,本文介绍另外一种方法run()方法,并给出一个实例参考。
1.基本原理
run()方法首先需要定义一个基于QThread的派生类,QThread类是用来创建和管理线程的类,它所依附的线程仍然在宿主线程(由parent指定),而它创建和管理的新线程即是run()启动的线程。也就是说基于QThread的派生类只有run函数是运行在新线程里的,这个类的其他所有函数都在这个类的宿主线程(由parent指定)中。
因此,要启动一个新线程,需要重写这个基于QThread的派生类的run(),当run()结束之后,这个线程也就终结了。一般情况下,run()里面包含一个死循环,将耗时运算放在run(),在run()中将运算的结果通过信号发射出来,而在宿主线程与之绑定的槽函数中处理运算结果。
QThread常用的方法和信号:
1)线程的启动和退出
线程的启动可使用如下函数:
void QThread::start(QThread::Priority priority = InheritPriority)
线程的退出可使用如下函数:
void QThread::quit()
线程退出过程中需等待事件处理完毕,可使用如下函数:
bool QThread::wait(unsigned long time = ULONG_MAX)
2)线程完成信号
线程完成后,会发射如下信号:
void QThread::finished()
通常将线程完成信号和线程里的对象销毁连接。参考代码如下:
connect(worker, &Worker::finished, worker, &QObject::deleteLater);
2.实例
1)定义基于QThread的派生类
这里定义一个Worker类,这个类基于QThread,参考代码如下:
#ifndef WORKER_H
#define WORKER_H
#include <QObject>
#include <QThread>
class Worker : public QThread
{
Q_OBJECT
public:
explicit Worker(QObject *parent = nullptr);
public:
void startWork();
void stopWork();
protected:
void run() override;
signals:
void resultReady();
public slots:
private:
bool bStarted;
};
#endif // WORKER_H
代码中重写了run()函数,并定义了一个signal将运算结果发射出来。
2)重写run()
run()函数一般是一个死循环,参考代码如下:
#include "worker.h"
#include <QDebug>
Worker::Worker(QObject *parent) : QThread(parent)
{
bStarted = false;
}
void Worker::startWork()
{
bStarted = true;
qDebug() << "start work func thread ID:" << QThread::currentThreadId();
}
void Worker::stopWork()
{
bStarted = true;
}
void Worker::run()
{
qDebug() << "run func thread ID:" << QThread::currentThreadId();
while (bStarted) {
Q_EMIT resultReady();
msleep(200);
}
quit();
}
这里为了方便展示run()所在的线程和Worker对象所在的线程,将它们的线程ID打印出来,同样,每隔200ms将运算结果发射出来。这里使用一个变量bStarted来退出线程。
3)宿主对象
宿主对象(这里的Controller对象)是位于主线程的,参考代码如下:
头文件:
#ifndef CONTROLLER_H
#define CONTROLLER_H
#include <QObject>
#include "worker.h"
class Controller : public QObject
{
Q_OBJECT
public:
explicit Controller(QObject *parent = nullptr);
~Controller();
signals:
public slots:
void handleResult();
private:
Worker *worker;
};
#endif // CONTROLLER_H
源文件:
#include "controller.h"
#include <QDebug>
Controller::Controller(QObject *parent) : QObject(parent)
{
worker = new Worker(this);
connect(worker, &Worker::resultReady, this, &Controller::handleResult);
connect(worker, &Worker::finished, worker, &QObject::deleteLater);
worker->start();
worker->startWork();
}
Controller::~Controller()
{
if (worker->isRunning()) {
worker->stopWork();
worker->wait();
}
}
void Controller::handleResult()
{
qDebug() << "handle result";
}
这里,
a)在Controller类中,指定Worker的父对象指针为this,即指向Controller,换句话说就是Worker里的线程(run())依附于Controller。
b)将Worker类的resultReady()和槽函数handleResult()绑定在一起,为了演示,这里只是打印消息,模拟线程处理结果。
c)在Controller类的析构函数中,判断线程是否在运行,退出线程,并等待与之相关的事件处理完毕。
4)主函数
主函数实例化一个Controller对象,并打印其所在线程ID,参考代码如下:
#include <QCoreApplication>
#include "controller.h"
#include <QDebug>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
qDebug() << "main thread ID:" << QThread::currentThreadId();
Controller controller;
return a.exec();
}
5)运行结果
运行结果如下图:
从运行结果可以看出:
主函数的线程ID和Work对象里的非run()的线程ID是一样的,而run()里面的线程ID和主函数里的ID不同,验证了前面的说法。
3.与moveToThread()方法差异
moveToThread()方法适合事件驱动型的处理。比如串口读写,数据的到来是异步事件,为了防止数据丢失,需要一个独立的线程去读数据。整个数据处理过程:数据到来->产生readyRead信号->对应的槽函数处理。
run()方法适合固定周期轮询型的处理。比如嵌入式程序需要固定的采样周期通过I2C读取传感器数据或读取GPIO状态。这时用run()方法是比较合适的。
总结,本文介绍了Qt多线程编程-run()方法。