QThread
来实现多线程方法,而是极力推崇继承
QObject
的方法来实现,当然用哪个方法实现要视情况而定,别弄错了就行,估计Qt如此推崇继承
QObject
的方法可能是
QThread
太容易用错的原因。
1. 前言
上一篇介绍了传统的多线程使用方法——继承QThread
来实现多线程,这也是很多框架的做法(MFC),但Qt还有一种多线程的实现方法,比直接继承QThread
更为灵活,就是直接继承QObject
实现多线程。
QObject
是Qt框架的基本类,但凡涉及到信号槽有关的类都是继承于QObject
。QObject
是一个功能异常强大的类,它提供了Qt关键技术信号和槽的支持以及事件系统的支持,同时它提供了线程操作的接口,也就是QObject
是可以选择不同的线程里执行的。
QObject
的线程转移函数是:void moveToThread(QThread * targetThread)
,通过此函数可以把一个顶层Object(就是没有父级)转移到一个新的线程里。
QThread
非常容易被新手误用,主要是QThread
自身并不生存在它run
函数所在的线程,而是生存在旧的线程中,此问题在上一篇重点描述了。由于QThread的这个特性,导致在调用QThread
的非run
函数容易在旧线程中执行,因此人们发现了一个新的魔改QThread
的方法:
人们发现,咦,QThread
也继承QObject
,QObject
有个函数void moveToThread(QThread * targetThread)
可以把Object的运行线程转移,那么:(下面是非常不推荐的魔改做法,别用此方法):
class MyThread : public QThread{
public:
MyThread ()
{
moveToThread(this);
}
……
};
直接把MyThread整个转移到MyThread的新线程中,MyThread
不仅run
,其它函数也在新线程里了。这样的确可以运行正常,但这并不是QThread
设计的初衷,Qt还专门发过一篇文章来吐槽这个做法。
在Qt4.8之后,Qt多线程的写法最好还是通过QObject
来实现,和线程的交互通过信号和槽(实际上其实是通过事件)联系。
2.继承QObject的多线程实现
用QObject
来实现多线程有个非常好的优点,就是默认就支持事件循环(Qt的许多非GUI类也需要事件循环支持,如QTimer
、QTcpSocket
),QThread
要支持事件循环需要在QThread::run()
中调用QThread::exec()
来提供对消息循环的支持,否则那些需要事件循环支持的类都不能正常发送信号,因此如果要使用信号和槽,那就直接使用QObject
来实现多线程。
2.1 创建及销毁线程
继承QObject
多线程的方法线程的创建很简单,只要让QThread
的start
函数运行起来就行,但是需要注意销毁线程的方法 在线程创建之后,这个QObject
的销毁不应该在主线程里进行,而是通过deleteLater
槽进行安全的销毁,因此,继承QObject
多线程的方法在创建时有几个槽函数需要特别关注:
- 一个是
QThread
的finished
信号对接QObject
的deleteLater
使得线程结束后,继承QObject
的那个多线程类会自己销毁 - 另一个是
QThread
的finished
信号对接QThread
自己的deleteLater
,这个不是必须,下面官方例子就没这样做
看看Qt官方文档的例子:
class Worker : public QObject
{
Q_OBJECT
public slots:
void doWork(const QString ¶meter) {
QString result;
/* ... here is the expensive or blocking operation ... */
emit resultReady(result);
}
signals:
void resultReady(const QString &result);
};
class Controller : public QObject
{
Q_OBJECT
QThread workerThread;
public:
Controller() {
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() {
workerThread.quit();
workerThread.wait();
}
public slots:
void handleResults(const QString &);
signals:
void operate(const QString &);
};
使用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
的方法更加灵活,整个类都是在新的线程中,通过信号槽和主线程传递数据