现在Qt官方并不是很推荐继承QThread来实现多线程方法,而是极力推崇继承QObject的方法来实现,当然用哪个方法实现要视情况而定,别弄错了就行,估计Qt如此推崇继承QObject的方法可能是QThread太容易用错的原因。
继承QThread实现多线程的方法点此
上一篇介绍了传统的多线程使用方法——继承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来实现,和线程的交互通过信号和槽(实际上其实是通过事件)联系。
用QObject来实现多线程有个非常好的优点,就是默认就支持事件循环(Qt的许多非GUI类也需要事件循环支持,如QTimer、QTcpSocket),QThread要支持事件循环需要在QThread::run()中调用QThread::exec()来提供对消息循环的支持,否则那些需要事件循环支持的类都不能正常发送信号,因此如果要使用信号和槽,那就直接使用QObject来实现多线程。
继承QObject多线程的方法线程的创建很简单,只要让QThread的start函数运行起来就行,但是需要注意销毁线程的方法 在线程创建之后,这个QObject的销毁不应该在主线程里进行,而是通过deleteLater槽进行安全的销毁,因此,继承QObject多线程的方法在创建时有几个槽函数需要特别关注:
一个是QThread的finished信号对接QObject的deleteLater使得线程结束后,继承QObject的那个多线程类会自己销毁另一个是QThread的finished信号对接QThread自己的deleteLater,这个不是必须,下面官方例子就没这样做看看Qt官方文档的例子:
class Worker : public QObject{ Q_OBJECTpublic 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的方法更加灵活,整个类都是在新的线程中,通过信号槽和主线程传递数据,前篇文章的例子用继承QObject的方法实现的话,代码如下: 头文件(ThreadObject.h):
#include <QObject>#include <QMutex>class ThreadObject : public QObject{ Q_OBJECTpublic: ThreadObject(QObject* parent = NULL); ~ThreadObject(); void setRunCount(int count); void stop();signals: void message(const QString& info); void progress(int present);public slots: void runSomeBigWork1(); void runSomeBigWork2();private: int m_runCount; int m_runCount2; bool m_isStop; QMutex m_stopMutex;};cpp文件(ThreadObject.cpp):
#include "ThreadObject.h"#include <QThread>#include <QDebug>#include <QMutexLocker>#include <QElapsedTimer>#include <limits>ThreadObject::ThreadObject(QObject *parent):QObject(parent) ,m_runCount(10) ,m_runCount2(std::numeric_limits<int>::max()) ,m_isStop(true){}ThreadObject::~ThreadObject(){ qDebug() << "ThreadObject destroy"; emit message(QString("Destroy %1->%2,thread id:%3").arg(__FUNCTION__).arg(__FILE__).arg((int)QThread::currentThreadId()));}void ThreadObject::setRunCount(int count){ m_runCount = count; emit message(QString("%1->%2,thread id:%3").arg(__FUNCTION__).arg(__FILE__).arg((int)QThread::currentThreadId()));}void ThreadObject::runSomeBigWork1(){ { QMutexLocker locker(&m_stopMutex); m_isStop = false; } int count = 0; QString str = QString("%1->%2,thread id:%3").arg(__FILE__).arg(__FUNCTION__).arg((int)QThread::currentThreadId()); emit message(str); int process = 0; while(1) { { QMutexLocker locker(&m_stopMutex); if(m_isStop) return; } if(m_runCount == count) { break; } sleep(1); int pro = ((float)count / m_runCount) * 100; if(pro != process) { process = pro; emit progress(((float)count / m_runCount) * 100); emit message(QString("Object::run times:%1,m_runCount:%2").arg(count).arg(m_runCount2)); } ++count; }}void ThreadObject::runSomeBigWork2(){ { QMutexLocker locker(&m_stopMutex); m_isStop = false; } int count = 0; QString str = QString("%1->%2,thread id:%3").arg(__FILE__).arg(__FUNCTION__).arg((int)QThread::currentThreadId()); emit message(str); int process = 0; QElapsedTimer timer; timer.start(); while(1) { { QMutexLocker locker(&m_stopMutex); if(m_isStop) return; } if(m_runCount2 == count) { break; } int pro = ((float)count / m_runCount2) * 100; if(pro != process) { process = pro; emit progress(pro); emit message(QString("%1,%2,%3,%4") .arg(count) .arg(m_runCount2) .arg(pro) .arg(timer.elapsed())); timer.restart(); } ++count; }}void ThreadObject::stop(){ QMutexLocker locker(&m_stopMutex); emit message(QString("%1->%2,thread id:%3").arg(__FUNCTION__).arg(__FILE__).arg((int)QThread::currentThreadId())); m_isStop = true;}这个Object有两个耗时函数work1和work2,这两个耗时函数的调用都是通过槽函数触发,同时为了能及时打断线程,添加了一个stop函数,stop函数不是通过信号槽触发,因此需要对数据进行保护,这里用了互斥锁对一个bool变量进行了保护处理,当然会失去一些性能。
主界面的头文件(截取部分代码):
#include <QWidget>#include <QTimer>class ThreadFromQThread;class ThreadObject;namespace Ui {class Widget;}class Widget : public QWidget{ Q_OBJECTpublic: explicit Widget(QWidget *parent = 0); ~Widget();signals: void startObjThreadWork1(); void startObjThreadWork2();private slots:…… void onButtonObjectMove2ThreadClicked(); void onButtonObjectMove2Thread2Clicked(); void onButtonObjectQuitClicked(); void onButtonObjectThreadStopClicked(); void progress(int val); void receiveMessage(const QString& str); void heartTimeOut();private: void startObjThread(); private: Ui::Widget *ui; …… ThreadObject* m_obj; QThread* m_objThread;};cpp文件
Widget::~Widget(){ qDebug() << "start destroy widget"; if(m_objThread) { m_objThread->quit(); } m_objThread->wait(); qDebug() << "end destroy widget";}//创建线程void Widget::startObjThread(){ if(m_objThread) { return; } m_objThread= new QThread(); m_obj = new ThreadObject(); m_obj->moveToThread(m_objThread); connect(m_objThread,&QThread::finished,m_objThread,&QObject::deleteLater); connect(m_objThread,&QThread::finished,m_obj,&QObject::deleteLater); connect(this,&Widget::startObjThreadWork1,m_obj,&ThreadObject::runSomeBigWork1); connect(this,&Widget::startObjThreadWork2,m_obj,&ThreadObject::runSomeBigWork2); connect(m_obj,&ThreadObject::progress,this,&Widget::progress); connect(m_obj,&ThreadObject::message,this,&Widget::receiveMessage); m_objThread->start();}//调用线程的runSomeBigWork1void Widget::onButtonObjectMove2ThreadClicked(){ if(!m_objThread) { startObjThread(); } emit startObjThreadWork1();//主线程通过信号换起子线程的槽函数 ui->textBrowser->append("start Obj Thread work 1");}//调用线程的runSomeBigWork2void Widget::onButtonObjectMove2Thread2Clicked(){ if(!m_objThread) { startObjThread(); } emit startObjThreadWork2();//主线程通过信号换起子线程的槽函数 ui->textBrowser->append("start Obj Thread work 2");}//调用线程的中断void Widget::onButtonObjectThreadStopClicked(){ if(m_objThread) { if(m_obj) { m_obj->stop(); } }}创建线程和官方例子差不多,区别是QThread也是用堆分配,这样,让QThread自杀的槽就一定记得加上,否则QThread就逍遥法外了。
connect(m_objThread,&QThread::finished,m_objThread,&QObject::deleteLater);上例的runSomeBigWork2中,让一个int不停自加1,一直加到int的最大值,为了验证加锁和不加锁的影响,这里对加锁和不加锁运行了两次观察耗时的变化
void ThreadObject::runSomeBigWork2(){ { QMutexLocker locker(&m_stopMutex); m_isStop = false; } int count = 0; QString str = QString("%1->%2,thread id:%3").arg(__FILE__).arg(__FUNCTION__).arg((int)QThread::currentThreadId()); emit message(str); int process = 0; QElapsedTimer timer; timer.start(); while(1) { { QMutexLocker locker(&m_stopMutex); if(m_isStop) return; } if(m_runCount2 == count) { break; } int pro = ((float)count / m_runCount2) * 100; if(pro != process) { process = pro; emit progress(pro); emit message(QString("%1,%2,%3,%4") .arg(count) .arg(m_runCount2) .arg(pro) .arg(timer.elapsed())); timer.restart(); } ++count; }}结果如下:
这里没个横坐标的每%1进行了21474837次循环,由统计图可见,Debug模式下使用了锁后性能下降4倍,Release模式下下降1.5倍的样子
# 3.总结
如果线程要用到消息循环,使用继承QObject的多线程方法更简单继承QObject的多线程不能指定父对象把所有耗时操作都作为槽函数QMutex会带来一定的耗时,大概速度会降低1.5倍(Release模式)–> 见 github