Qt使用多线程的一些心得——2.继承QObject的多线程使用方法

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/czyt1988/article/details/71194457

现在Qt官方并不是很推荐继承QThread来实现多线程方法,而是极力推崇继承QObject的方法来实现,当然用哪个方法实现要视情况而定,别弄错了就行,估计Qt如此推崇继承QObject的方法可能是QThread太容易用错的原因。

继承QThread实现多线程的方法点此

1. 前言

上一篇介绍了传统的多线程使用方法——继承QThread来实现多线程,这也是很多框架的做法(MFC),但Qt还有一种多线程的实现方法,比直接继承QThread更为灵活,就是直接继承QObject实现多线程。

QObject是Qt框架的基本类,但凡涉及到信号槽有关的类都是继承于QObjectQObject是一个功能异常强大的类,它提供了Qt关键技术信号和槽的支持以及事件系统的支持,同时它提供了线程操作的接口,也就是QObject是可以选择不同的线程里执行的。

QObject的线程转移函数是:void moveToThread(QThread * targetThread) ,通过此函数可以把一个顶层Object(就是没有父级)转移到一个新的线程里。

QThread非常容易被新手误用,主要是QThread自身并不生存在它run函数所在的线程,而是生存在旧的线程中,此问题在上一篇重点描述了。由于QThread的这个特性,导致在调用QThread的非run函数容易在旧线程中执行,因此人们发现了一个新的魔改QThread的方法: 
人们发现,咦,QThread也继承QObjectQObject有个函数void moveToThread(QThread * targetThread)可以把Object的运行线程转移,那么:(下面是非常不推荐的魔改做法,别用此方法):

  
  
  1. class MyThread : public QThread{
  2. public:
  3. MyThread ()
  4. {
  5. moveToThread(this);
  6. }
  7. ……
  8. };

直接把MyThread整个转移到MyThread的新线程中,MyThread不仅run,其它函数也在新线程里了。这样的确可以运行正常,但这并不是QThread设计的初衷,Qt还专门发过一篇文章来吐槽这个做法。

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

2.继承QObject的多线程实现

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

2.1 创建及销毁线程

继承QObject多线程的方法线程的创建很简单,只要让QThreadstart函数运行起来就行,但是需要注意销毁线程的方法 
在线程创建之后,这个QObject的销毁不应该在主线程里进行,而是通过deleteLater槽进行安全的销毁,因此,继承QObject多线程的方法在创建时有几个槽函数需要特别关注:

  • 一个是QThreadfinished信号对接QObjectdeleteLater使得线程结束后,继承QObject的那个多线程类会自己销毁
  • 另一个是QThreadfinished信号对接QThread自己的deleteLater,这个不是必须,下面官方例子就没这样做

看看Qt官方文档的例子:

  
  
  1. class Worker : public QObject
  2. {
  3. Q_OBJECT
  4. public slots:
  5. void doWork(const QString &parameter) {
  6. QString result;
  7. /* ... here is the expensive or blocking operation ... */
  8. emit resultReady(result);
  9. }
  10. signals:
  11. void resultReady(const QString &result);
  12. };
  13. class Controller : public QObject
  14. {
  15. Q_OBJECT
  16. QThread workerThread;
  17. public:
  18. Controller() {
  19. Worker *worker = new Worker;
  20. worker->moveToThread(&workerThread);
  21. connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);
  22. connect(this, &Controller::operate, worker, &Worker::doWork);
  23. connect(worker, &Worker::resultReady, this, &Controller::handleResults);
  24. workerThread.start();
  25. }
  26. ~Controller() {
  27. workerThread.quit();
  28. workerThread.wait();
  29. }
  30. public slots:
  31. void handleResults(const QString &);
  32. signals:
  33. void operate(const QString &);
  34. };

使用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):

  
  
  1. #include <QObject>
  2. #include <QMutex>
  3. class ThreadObject : public QObject
  4. {
  5. Q_OBJECT
  6. public:
  7. ThreadObject(QObject* parent = NULL);
  8. ~ThreadObject();
  9. void setRunCount(int count);
  10. void stop();
  11. signals:
  12. void message(const QString& info);
  13. void progress(int present);
  14. public slots:
  15. void runSomeBigWork1();
  16. void runSomeBigWork2();
  17. private:
  18. int m_runCount;
  19. int m_runCount2;
  20. bool m_isStop;
  21. QMutex m_stopMutex;
  22. };

cpp文件(ThreadObject.cpp):

  
  
  1. #include "ThreadObject.h"
  2. #include <QThread>
  3. #include <QDebug>
  4. #include <QMutexLocker>
  5. #include <QElapsedTimer>
  6. #include <limits>
  7. ThreadObject::ThreadObject(QObject *parent):QObject(parent)
  8. ,m_runCount(10)
  9. ,m_runCount2(std::numeric_limits<int>::max())
  10. ,m_isStop(true)
  11. {
  12. }
  13. ThreadObject::~ThreadObject()
  14. {
  15. qDebug() << "ThreadObject destroy";
  16. emit message(QString("Destroy %1->%2,thread id:%3").arg(__FUNCTION__).arg(__FILE__).arg((int)QThread::currentThreadId()));
  17. }
  18. void ThreadObject::setRunCount(int count)
  19. {
  20. m_runCount = count;
  21. emit message(QString("%1->%2,thread id:%3").arg(__FUNCTION__).arg(__FILE__).arg((int)QThread::currentThreadId()));
  22. }
  23. void ThreadObject::runSomeBigWork1()
  24. {
  25. {
  26. QMutexLocker locker(&m_stopMutex);
  27. m_isStop = false;
  28. }
  29. int count = 0;
  30. QString str = QString("%1->%2,thread id:%3").arg(__FILE__).arg(__FUNCTION__).arg((int)QThread::currentThreadId());
  31. emit message(str);
  32. int process = 0;
  33. while(1)
  34. {
  35. {
  36. QMutexLocker locker(&m_stopMutex);
  37. if(m_isStop)
  38. return;
  39. }
  40. if(m_runCount == count)
  41. {
  42. break;
  43. }
  44. sleep(1);
  45. int pro = ((float)count / m_runCount) * 100;
  46. if(pro != process)
  47. {
  48. process = pro;
  49. emit progress(((float)count / m_runCount) * 100);
  50. emit message(QString("Object::run times:%1,m_runCount:%2").arg(count).arg(m_runCount2));
  51. }
  52. ++count;
  53. }
  54. }
  55. void ThreadObject::runSomeBigWork2()
  56. {
  57. {
  58. QMutexLocker locker(&m_stopMutex);
  59. m_isStop = false;
  60. }
  61. int count = 0;
  62. QString str = QString("%1->%2,thread id:%3").arg(__FILE__).arg(__FUNCTION__).arg((int)QThread::currentThreadId());
  63. emit message(str);
  64. int process = 0;
  65. QElapsedTimer timer;
  66. timer.start();
  67. while(1)
  68. {
  69. {
  70. QMutexLocker locker(&m_stopMutex);
  71. if(m_isStop)
  72. return;
  73. }
  74. if(m_runCount2 == count)
  75. {
  76. break;
  77. }
  78. int pro = ((float)count / m_runCount2) * 100;
  79. if(pro != process)
  80. {
  81. process = pro;
  82. emit progress(pro);
  83. emit message(QString("%1,%2,%3,%4")
  84. .arg(count)
  85. .arg(m_runCount2)
  86. .arg(pro)
  87. .arg(timer.elapsed()));
  88. timer.restart();
  89. }
  90. ++count;
  91. }
  92. }
  93. void ThreadObject::stop()
  94. {
  95. QMutexLocker locker(&m_stopMutex);
  96. emit message(QString("%1->%2,thread id:%3").arg(__FUNCTION__).arg(__FILE__).arg((int)QThread::currentThreadId()));
  97. m_isStop = true;
  98. }

这个Object有两个耗时函数work1和work2,这两个耗时函数的调用都是通过槽函数触发,同时为了能及时打断线程,添加了一个stop函数,stop函数不是通过信号槽触发,因此需要对数据进行保护,这里用了互斥锁对一个bool变量进行了保护处理,当然会失去一些性能。

主界面的头文件(截取部分代码):

  
  
  1. #include <QWidget>
  2. #include <QTimer>
  3. class ThreadFromQThread;
  4. class ThreadObject;
  5. namespace Ui {
  6. class Widget;
  7. }
  8. class Widget : public QWidget
  9. {
  10. Q_OBJECT
  11. public:
  12. explicit Widget(QWidget *parent = 0);
  13. ~Widget();
  14. signals:
  15. void startObjThreadWork1();
  16. void startObjThreadWork2();
  17. private slots:
  18. ……
  19. void onButtonObjectMove2ThreadClicked();
  20. void onButtonObjectMove2Thread2Clicked();
  21. void onButtonObjectQuitClicked();
  22. void onButtonObjectThreadStopClicked();
  23. void progress(int val);
  24. void receiveMessage(const QString& str);
  25. void heartTimeOut();
  26. private
  27. void startObjThread();
  28. private:
  29. Ui::Widget *ui;
  30. ……
  31. ThreadObject* m_obj;
  32. QThread* m_objThread;
  33. };

cpp文件

  
  
  1. Widget::~Widget()
  2. {
  3. qDebug() << "start destroy widget";
  4. if(m_objThread)
  5. {
  6. m_objThread->quit();
  7. }
  8. m_objThread->wait();
  9. qDebug() << "end destroy widget";
  10. }
  11. //创建线程
  12. void Widget::startObjThread()
  13. {
  14. if(m_objThread)
  15. {
  16. return;
  17. }
  18. m_objThread= new QThread();
  19. m_obj = new ThreadObject();
  20. m_obj->moveToThread(m_objThread);
  21. connect(m_objThread,&QThread::finished,m_objThread,&QObject::deleteLater);
  22. connect(m_objThread,&QThread::finished,m_obj,&QObject::deleteLater);
  23. connect(this,&Widget::startObjThreadWork1,m_obj,&ThreadObject::runSomeBigWork1);
  24. connect(this,&Widget::startObjThreadWork2,m_obj,&ThreadObject::runSomeBigWork2);
  25. connect(m_obj,&ThreadObject::progress,this,&Widget::progress);
  26. connect(m_obj,&ThreadObject::message,this,&Widget::receiveMessage);
  27. m_objThread->start();
  28. }
  29. //调用线程的runSomeBigWork1
  30. void Widget::onButtonObjectMove2ThreadClicked()
  31. {
  32. if(!m_objThread)
  33. {
  34. startObjThread();
  35. }
  36. emit startObjThreadWork1();//主线程通过信号换起子线程的槽函数
  37. ui->textBrowser->append("start Obj Thread work 1");
  38. }
  39. //调用线程的runSomeBigWork2
  40. void Widget::onButtonObjectMove2Thread2Clicked()
  41. {
  42. if(!m_objThread)
  43. {
  44. startObjThread();
  45. }
  46. emit startObjThreadWork2();//主线程通过信号换起子线程的槽函数
  47. ui->textBrowser->append("start Obj Thread work 2");
  48. }
  49. //调用线程的中断
  50. void Widget::onButtonObjectThreadStopClicked()
  51. {
  52. if(m_objThread)
  53. {
  54. if(m_obj)
  55. {
  56. m_obj->stop();
  57. }
  58. }
  59. }

创建线程和官方例子差不多,区别是QThread也是用堆分配,这样,让QThread自杀的槽就一定记得加上,否则QThread就逍遥法外了。

  
  
  1. connect(m_objThread,&QThread::finished,m_objThread,&QObject::deleteLater);

3.加锁对性能的影响

上例的runSomeBigWork2中,让一个int不停自加1,一直加到int的最大值,为了验证加锁和不加锁的影响,这里对加锁和不加锁运行了两次观察耗时的变化

  
  
  1. void ThreadObject::runSomeBigWork2()
  2. {
  3. {
  4. QMutexLocker locker(&m_stopMutex);
  5. m_isStop = false;
  6. }
  7. int count = 0;
  8. QString str = QString("%1->%2,thread id:%3").arg(__FILE__).arg(__FUNCTION__).arg((int)QThread::currentThreadId());
  9. emit message(str);
  10. int process = 0;
  11. QElapsedTimer timer;
  12. timer.start();
  13. while(1)
  14. {
  15. {
  16. QMutexLocker locker(&m_stopMutex);
  17. if(m_isStop)
  18. return;
  19. }
  20. if(m_runCount2 == count)
  21. {
  22. break;
  23. }
  24. int pro = ((float)count / m_runCount2) * 100;
  25. if(pro != process)
  26. {
  27. process = pro;
  28. emit progress(pro);
  29. emit message(QString("%1,%2,%3,%4")
  30. .arg(count)
  31. .arg(m_runCount2)
  32. .arg(pro)
  33. .arg(timer.elapsed()));
  34. timer.restart();
  35. }
  36. ++count;
  37. }
  38. }

结果如下: 




这里没个横坐标的每%1进行了21474837次循环,由统计图可见,Debug模式下使用了锁后性能下降4倍,Release模式下下降1.5倍的样子

# 3.总结

  • 如果线程要用到消息循环,使用继承QObject的多线程方法更简单
  • 继承QObject的多线程不能指定父对象
  • 把所有耗时操作都作为槽函数
  • QMutex会带来一定的耗时,大概速度会降低1.5倍(Release模式)

–> 见 github


猜你喜欢

转载自blog.csdn.net/czyt1988/article/details/71194457