Qt之多线程编程(二)

现在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的运行线程转移,那么:(下面是非常不推荐的魔改做法,别用此方法):

class MyThread : public QThread{
public:
MyThread ()
{
   moveToThread(this);
}
……
};

 

直接把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官方文档的例子: 

class Worker : public QObject
{
   Q_OBJECT

public slots:
   void doWork(const QString &parameter) {
      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_OBJECT
public:
   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_OBJECT

public:
   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();
}

//调用线程的runSomeBigWork1
void Widget::onButtonObjectMove2ThreadClicked()
{
   if(!m_objThread)
   {
      startObjThread();
   }

   emit startObjThreadWork1();//主线程通过信号换起子线程的槽函数

   ui->textBrowser->append("start Obj Thread work 1");
}
//调用线程的runSomeBigWork2
void 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);

3.加锁对性能的影响

上例的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模式)
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值