目的
在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控件在线程里,进行定时执行,也是可以的。
最后用一图进行总结: