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

70 篇文章 2 订阅

转自
https://blog.csdn.net/czyt1988/article/details/71194457

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

继承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 &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

include

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

include

include

include

include

ThreadObject::ThreadObject(QObject *parent):QObject(parent)
,m_runCount(10)
,m_runCount2(std::numeric_limits::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

include

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模式)
–> 见 github

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值