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

2024-01-07 00:38

本文主要是介绍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框架的基本类,但凡涉及到信号槽有关的类都是继承于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

这篇关于Qt使用多线程的一些心得——2.继承QObject的多线程使用方法的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/578165

相关文章

C语言中联合体union的使用

本文编辑整理自: http://bbs.chinaunix.net/forum.php?mod=viewthread&tid=179471 一、前言 “联合体”(union)与“结构体”(struct)有一些相似之处。但两者有本质上的不同。在结构体中,各成员有各自的内存空间, 一个结构变量的总长度是各成员长度之和。而在“联合”中,各成员共享一段内存空间, 一个联合变量

关于C++中的虚拟继承的一些总结(虚拟继承,覆盖,派生,隐藏)

1.为什么要引入虚拟继承 虚拟继承是多重继承中特有的概念。虚拟基类是为解决多重继承而出现的。如:类D继承自类B1、B2,而类B1、B2都继承自类A,因此在类D中两次出现类A中的变量和函数。为了节省内存空间,可以将B1、B2对A的继承定义为虚拟继承,而A就成了虚拟基类。实现的代码如下: class A class B1:public virtual A; class B2:pu

问题:第一次世界大战的起止时间是 #其他#学习方法#微信

问题:第一次世界大战的起止时间是 A.1913 ~1918 年 B.1913 ~1918 年 C.1914 ~1918 年 D.1914 ~1919 年 参考答案如图所示

[word] word设置上标快捷键 #学习方法#其他#媒体

word设置上标快捷键 办公中,少不了使用word,这个是大家必备的软件,今天给大家分享word设置上标快捷键,希望在办公中能帮到您! 1、添加上标 在录入一些公式,或者是化学产品时,需要添加上标内容,按下快捷键Ctrl+shift++就能将需要的内容设置为上标符号。 word设置上标快捷键的方法就是以上内容了,需要的小伙伴都可以试一试呢!

Tolua使用笔记(上)

目录   1.准备工作 2.运行例子 01.HelloWorld:在C#中,创建和销毁Lua虚拟机 和 简单调用。 02.ScriptsFromFile:在C#中,对一个lua文件的执行调用 03.CallLuaFunction:在C#中,对lua函数的操作 04.AccessingLuaVariables:在C#中,对lua变量的操作 05.LuaCoroutine:在Lua中,

大学湖北中医药大学法医学试题及答案,分享几个实用搜题和学习工具 #微信#学习方法#职场发展

今天分享拥有拍照搜题、文字搜题、语音搜题、多重搜题等搜题模式,可以快速查找问题解析,加深对题目答案的理解。 1.快练题 这是一个网站 找题的网站海量题库,在线搜题,快速刷题~为您提供百万优质题库,直接搜索题库名称,支持多种刷题模式:顺序练习、语音听题、本地搜题、顺序阅读、模拟考试、组卷考试、赶快下载吧! 2.彩虹搜题 这是个老公众号了 支持手写输入,截图搜题,详细步骤,解题必备

Vim使用基础篇

本文内容大部分来自 vimtutor,自带的教程的总结。在终端输入vimtutor 即可进入教程。 先总结一下,然后再分别介绍正常模式,插入模式,和可视模式三种模式下的命令。 目录 看完以后的汇总 1.正常模式(Normal模式) 1.移动光标 2.删除 3.【:】输入符 4.撤销 5.替换 6.重复命令【. ; ,】 7.复制粘贴 8.缩进 2.插入模式 INSERT

电脑不小心删除的文件怎么恢复?4个必备恢复方法!

“刚刚在对电脑里的某些垃圾文件进行清理时,我一不小心误删了比较重要的数据。这些误删的数据还有机会恢复吗?希望大家帮帮我,非常感谢!” 在这个数字化飞速发展的时代,电脑早已成为我们日常生活和工作中不可或缺的一部分。然而,就像生活中的小插曲一样,有时我们可能会在不经意间犯下一些小错误,比如不小心删除了重要的文件。 当那份文件消失在眼前,仿佛被时间吞噬,我们不禁会心生焦虑。但别担心,就像每个问题

Lipowerline5.0 雷达电力应用软件下载使用

1.配网数据处理分析 针对配网线路点云数据,优化了分类算法,支持杆塔、导线、交跨线、建筑物、地面点和其他线路的自动分类;一键生成危险点报告和交跨报告;还能生成点云数据采集航线和自主巡检航线。 获取软件安装包联系邮箱:2895356150@qq.com,资源源于网络,本介绍用于学习使用,如有侵权请您联系删除! 2.新增快速版,简洁易上手 支持快速版和专业版切换使用,快速版界面简洁,保留主

如何免费的去使用connectedpapers?

免费使用connectedpapers 1. 打开谷歌浏览器2. 按住ctrl+shift+N,进入无痕模式3. 不需要登录(也就是访客模式)4. 两次用完,关闭无痕模式(继续重复步骤 2 - 4) 1. 打开谷歌浏览器 2. 按住ctrl+shift+N,进入无痕模式 输入网址:https://www.connectedpapers.com/ 3. 不需要登录(也就是