Qt应用开发(进阶篇)——线程 QThread

一、前言

        QThread类继承于QObject基类,是Qt经典基础工具类,QThread类提供了一种独立于平台的方式来管理线程,让开发者能够快速的完成多线程的创建和使用。

        正常情况下,一个PC程序使用到多线程的概率是非常高的,在不同方式的通讯场景使用、在耗时任务中使用、在独立的任务中使用等等。所以学习好多线程的使用是非常重要的,这也是程序员必备的技能之一。在C++中也有线程的功能,但是Qt提供的QThread线程,更适用于在Qt框架中使用。

        QThread对象管理一个独立的线程,调用start()启用,启用成功触发started()信号,当线程结束的时候触发finished()信号,并提供isFinished()、isRunning()查询状态。使用exit()quit()主动退出线程,wait()阻塞等待线程结束。

        QThread线程还可以使用setPriority()设置优先级,而优先级参数的效果取决于操作系统的调度策略,在一些不支持线程优先的操作系统,比如Linux,优先级将被忽略。

二、创建线程方法一

        QThreads在run()中开始执行,默认情况下,run()通过调用exec()启动事件循环,并在线程内运行Qt事件循环,当run函数执行完成的时候,线程执行结束触发信号并退出。

        所以创建线程的第一种方式就是继承QThread,重新实现run函数。

#ifndef MYTHREAD_H
#define MYTHREAD_H#include <QThread>
#include <QObject>class MyThread : public QThread
{Q_OBJECT
public:explicit MyThread(QObject *parent = nullptr);~MyThread();
protected:virtual void run();
Q_SIGNALS:void printMsg(int);
};#endif // MYTHREAD_H
#include "mythread.h"
#include <QDebug>
MyThread::MyThread(QObject *parent) : QThread(parent)
{}
MyThread::~MyThread()
{qDebug()<<"~MyThread ";
}
void MyThread::run()
{qDebug()<<"MyThread "<<this->currentThreadId();for(int i = 0 ; i < 5 ;i++){qDebug()<<i;emit printMsg(i*2);}
}
qDebug()<<"main thread id"<<QThread::currentThreadId();
thread = new MyThread(this);
thread->start();
connect(thread,&MyThread::finished,this,[](){qDebug()<<"thread finish";
});
connect(thread,&MyThread::printMsg,this,[](int num){qDebug()<<"thread Msg "<<num;
});

        在上面的例子中,我们继承QThread,重新实现Run函数,在线程中打印数字,并且抛出自定义的信号printMsg,主线程绑定信号打印信息。线程结束后QThread会触发finished信号,我们也绑定该信号打印信息,当主程序结束后,线程类调用析构函数并打印信息。

main thread id 0x7ffb77158040
MyThread  0x7ffb47767700
0
1
2
3
4
thread Msg  0
thread Msg  2
thread Msg  4
thread Msg  6
thread Msg  8
thread finish
~MyThread 

        但是打印的结果并不是我们想要的,因为我们打印数字的同时一边在抛出信号,为什么会打印结束了,才开始执行槽函数呢,并没有两边交替打印,这是因为信号槽的连接方式默认为Qt::AutoConnection,而如果是线程之间的连接,会自动转换成Qt::QueuedConnection(当控制返回到接收者线程的事件循环时调用该槽,槽在接收者的线程中执行)。所以我们需要修改连接方式。

connect(thread,&MyThread::printMsg,this,[](int num){qDebug()<<"thread Msg "<<num;
},Qt::DirectConnection);
main thread id 0x7f2e6825d040
MyThread  0x7f2e38a32700
0
thread Msg  0
1
thread Msg  2
2
thread Msg  4
3
thread Msg  6
4
thread Msg  8
thread finish
~MyThread 

二、创建线程方法二

        使用QObject::moveToThread()将工作对象移动到线程中,是Qt线程使用的第二种方式。

#ifndef WORKER_H
#define WORKER_H#include <QObject>
#include <QDebug>
#include <QThread>
class worker : public QObject
{Q_OBJECT
public:explicit worker(QObject *parent = nullptr);
Q_SIGNALS:void resultReady(QString result);
public Q_SLOTS:void doWork(QString parameter);
};#endif // WORKER_H
#include "worker.h"worker::worker(QObject *parent) : QObject(parent)
{}
void worker::doWork(QString parameter) {qDebug()<<QThread::currentThreadId();QString result;qDebug()<<"work doWork"<<parameter;emit resultReady(result);
}

        定义完工作类,我们要定义一个线程,并使用moveToThread把工作类“移动”到线程中。

class MainWindow : public QMainWindow
{Q_OBJECT
public:MainWindow(QWidget *parent = nullptr);~MainWindow();
Q_SIGNALS:void doWork(QString);
private slots:void on_pushButton_clicked();private:QThread *m_thread;worker *m_work;
};
MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow)
{ui->setupUi(this);m_thread = new QThread(this);m_work = new worker();m_work->moveToThread(m_thread);connect(this,&MainWindow::doWork,m_work,&worker::doWork);connect(m_work,&worker::resultReady,this,[&](QString result){qDebug()<<"recv m_work Msg "<<result;});m_thread->start();
}
void MainWindow::on_pushButton_clicked()
{qDebug()<<"main thread id"<<QThread::currentThreadId();emit doWork("hello world");
}

         执行结果:

main thread id 0x7f016253c040
0x7f013bfff700
work doWork "hello world"
recv m_work Msg  ""

三、线程使用注意事项

1、CPU飙升

        初次使用QThread的同学,经常会出现一个问题,在线程执行的函数体中,使用死循环一直读串口或者后台等一些数据信息,读到信息抛出信号,读不到一直调用自定义读取的函数。在这种工况下,如果读取的函数是非阻塞的,那么整个CPU的资源都被子线程占用着,系统没办法合理的分配时间片。所以如果有需要使用读取线程并且用死循环读取信息的同学,一定要确保读取的函数是阻塞的或者做sleep操作。

2、阻塞线程退出异常

        正常情况下,读取线程我们会定义一个变量,用来控制死循环的结束兼线程的结束。

bool m_loop;
void MyThread::run()
{qDebug()<<"MyThread "<<this->currentThreadId();m_loop = true;while (m_loop) {qDebug()<<10;sleep(1);}
}
void MyThread::stop()
{m_loop = false;
}
thread->stop();

        在上面的实例中,我们使用m_loop变量来结束死循环,让run函数结束,从而控制线程的结束。

        但是如果是阻塞的场景下,这样的逻辑就不够用了。直接退出会报“QThread: Destroyed while thread is still running”。

        因为死循环中,读取函数一直卡着,这样就没办法退出,这时候我们需要注意阻塞的退出方法,比如waitForReadyRead默认为3秒、linux中的select设置超时、socketcan使用阻塞则用关闭退出等等。配合使用QThread的wait函数,会阻塞在该函数等待线程的退出,让主线程退出子线程的时候做出等待的操作。

thread->stop();
thread->quit();
thread->wait();

3、moveToThread方式的线程退出

        使用此方式进行创建线程,它不像重写run一样,run函数结束线程就自动退出。我们一开始调用的是start,在程序中它一直都处于运行的状态,只是如果你没有使用信号触发,它会处于休眠状态。所以在程序结束的时候记得使用quit和wait退出线程,是否会报“QThread: Destroyed while thread is still running”。

thread->quit();
thread->wait();

4、moveToThread方式的工作类不能有父类

        在第二例子中,在定义worker类的时候,不能写成:

m_work = new worker(this);

        否则会有告警:

QObject::moveToThread: Cannot move objects with a parent

        并且运行之后发现,work的函数调用其实是在主线程中,并没有在子线程中执行。

main thread id 0x7f5299998040
0x7f5299998040
work doWork "hello world"
recv m_work Msg  ""

5、线程中使用成员类异常告警

        问题代码如下:

#ifndef MYTHREAD_H
#define MYTHREAD_H#include <QThread>
#include <QObject>
#include <QTimer>
class MyThread : public QThread
{Q_OBJECT
public:explicit MyThread(QObject *parent = nullptr);~MyThread();void stop();
protected:virtual void run();
Q_SIGNALS:void printMsg(int);
private:QTimer *m_timer;bool m_loop;
};#endif // MYTHREAD_H
#include "mythread.h"
#include <QDebug>
MyThread::MyThread(QObject *parent) : QThread(parent)
{m_timer = new QTimer(this);connect(m_timer,&QTimer::timeout,this,[](){qDebug()<<"time out";});
}
MyThread::~MyThread()
{qDebug()<<"~MyThread ";
}
void MyThread::run()
{qDebug()<<"MyThread "<<this->currentThreadId();m_loop = true;m_timer->start(1000);while (m_loop) {qDebug()<<10;sleep(1);}
}
void MyThread::stop()
{m_loop = false;
}

        在上面的例子中,MyThread成员类变量QTimer,在构造函数中实例化,在run函数中启动,这时候线程启动的时候会报异常:

QObject::startTimer: Timers cannot be started from another thread

        这是因为在重写run的这种方式中,除了run函数内其他函数包括类都是属于主线程的,包括构造函数。所以定时器是属于主线程的类,在子线程中控制它,就会告警,于是我们修改定时器定义:

void MyThread::run()
{qDebug()<<"MyThread "<<this->currentThreadId();m_loop = true;m_timer = new QTimer(this);connect(m_timer,&QTimer::timeout,this,[](){qDebug()<<"time out";});m_timer->start(1000);while (m_loop) {qDebug()<<10;sleep(1);}
}

        这时候又会报另一个错误,不能在子线程中为主线程的类创建子类。

QObject: Cannot create children for a parent that is in a different thread.
(Parent is MyThread(0x561d014478b0), parent's thread is QThread(0x561d01438e20), current thread is MyThread(0x561d014478b0)

        所以我们需要去掉this指针,就不会有这个错误,这时候需要注意指针的释放,因为我们没有定义父类,它不会跟随父类的释放而释放。

void MyThread::run()
{qDebug()<<"MyThread "<<this->currentThreadId();m_loop = true;QTimer *m_timer = new QTimer();connect(m_timer,&QTimer::timeout,this,[](){qDebug()<<"time out";});m_timer->start(1000);while (m_loop) {qDebug()<<10;sleep(1);}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/158694.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

bclinux aarch64 ceph 14.2.10 云主机 4节点 fio

ceph -s 由于是基于底层分布式存储的云主机&#xff0c;数据仅供参考 本地云盘性能 direct1 1M读取 IOPS134, BW134MiB/s [rootceph-client rbd]# cd / [rootceph-client /]# fio -filenamefio.bin -direct1 -iodepth 128 -thread -rwread -ioenginelibaio -bs1M -size10G -n…

Java继承和多态

文章目录 继承成员继承构造方法super和this的区别代码块构造顺序 限定修饰符final继承和组合 多态向上转型重写动态绑定重写注意事项 向下转型多态优点注意 继承 成员继承 class Animal{public String name;public int age;public int a10;public void doSomething(){System.…

KDE 项目发布了 KDE Gear 23.08.3

导读KDE 项目发布了 KDE Gear 23.08.3&#xff0c;作为最新的 KDE Gear 23.08 开源集合的第三次维护更新&#xff0c;该集合包含了用于 KDE Plasma 桌面环境和其他平台的 KDE 应用程序。 KDE Gear 23.08.3 是在 KDE Gear 23.08.2 大约一个月之后发布的&#xff0c;包含了更多对…

系列五、为什么不用线程id作为ThreadLocalMap的key

一、为什么不用线程id作为ThreadLocalMap的key 1.1、案例代码 /*** Author : 一叶浮萍归大海* Date: 2023/11/21 11:50* Description: 需求&#xff1a;* 如果当前线程是线程1&#xff0c;那么设置书名和作者分别为 三国演义 罗贯中* 如果…

“高校评分”走红网络,虎扑:若造谣抹黑,学校可联系平台处理

哎呀&#xff0c;最近虎扑APP的全国高校评分可是火遍了网络啊&#xff01;那些机智的评语&#xff0c;哦哟&#xff0c;都成了新的“网络爆款梗”&#xff01;有毕业生说嘛&#xff0c;这评分都是看学生自己的经历和感受&#xff0c;有好评当然就有差评啦。但关键是&#xff0c…

Django 入门学习总结3

1、创建数据库 打开mysite/settings.py文件&#xff0c;可以看到我们使用Python中已包含的默认的数据库SQLite&#xff0c;也可以使用其他的数据库&#xff0c;如Oracle、Mysql等。里面也包含时区、语言等设置信息。 在使用数据库和表之前&#xff0c;输入下面的命令&#xf…

HT5169 单声道D类音频功放 I2S输入

HT5169是一款内置BOOST升压模块的D类音频功率放大器。内置的BOOST升压模块可通过外置电阻调节升压值&#xff0c;即使是锂电池供电&#xff0c;在升压至7.5V&#xff0c;2Ω负载条件下则能连续输出 11W功率。其支持外部设置调节BOOST输出电压。 HT5169是一颗单声道D类音频功放&…

Android使用Kotlin利用Gson解析多层嵌套Json数据

文章目录 1、依赖2、解析 1、依赖 build.gradle(app)中加入 dependencies { implementation com.google.code.gson:gson:2.8.9 }2、解析 假设这是要解析Json数据 var responseStr "{"code": 200,"message": "操作成功","data&quo…

A____Z____RECOVER____DATA勒索恢复---惜分飞

有客户MySQL数据库被黑,业务库中表被删除,并创建A____Z____RECOVER____DATA库,里面有一张readme表,内容为: mysql> select * from readme \G; *************************** 1. row *************************** zh_content: 请尽快与我们取得联系&#xff0c;否则我们将会公…

第28期 | GPTSecurity周报

GPTSecurity是一个涵盖了前沿学术研究和实践经验分享的社区&#xff0c;集成了生成预训练Transformer&#xff08;GPT&#xff09;、人工智能生成内容&#xff08;AIGC&#xff09;以及大型语言模型&#xff08;LLM&#xff09;等安全领域应用的知识。在这里&#xff0c;您可以…

【linux】进行间通信——共享内存+消息队列+信号量

共享内存消息队列信号量 1.共享内存1.1共享内存的原理1.2共享内存的概念1.3接口的认识1.4实操comm.hppservice.cc &#xff08;写&#xff09;clint.cc &#xff08;读&#xff09; 1.5共享内存的总结1.6共享内存的内核结构 2.消息队列2.1原理2.2接口 3.信号量3.1信号量是什么3…

如何开发干洗店用的小程序

洗护行业现在都开始往线上的方向发展了&#xff0c;越来越多的干洗店都推出了上门取送服务&#xff0c;那么就需要开发一个干洗店专用的小程序去作为用户和商家的桥梁&#xff0c;这样的小程序该如何开发呢&#xff1f; 一、功能设计&#xff1a;根据干洗店的业务需求和小程序的…

算法-二叉树-简单-二叉树的直径、将有序数组转换成二叉搜索树

记录一下算法题的学习9 二叉树的直径 题目&#xff1a;给你一棵二叉树的根节点&#xff0c;返回该树的 直径 。 二叉树的 直径 是指树中任意两个节点之间最长路径的 长度 。这条路径可能经过也可能不经过根节点 root 。两节点之间路径的 长度 由它们之间边数表示 读完题目&…

2014年3月24日 Go生态洞察:Go地鼠的故事

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

PRD学习

产品经理零基础入门&#xff08;五&#xff09;产品需求文档PRD&#xff08;全16集&#xff09;_哔哩哔哩_bilibili 1. PRD的2种表现形式 ① RP格式 &#xff08;1&#xff09;全局说明 ② 文档格式

redis之主从复制和哨兵模式

&#xff08;一&#xff09;redis的性能管理 1、redis的数据缓存在内存中 2、查看redis的性能&#xff1a;info memory&#xff08;重点&#xff09; used_memory:904192&#xff08;单位字节&#xff09; redis中数据占用的内存 used_memory_rss:10522624 redis向操作系统…

广告行业中那些趣事系列66:使用chatgpt类LLM标注数据并蒸馏到生产小模型

导读&#xff1a;本文是“数据拾光者”专栏的第六十六篇文章&#xff0c;这个系列将介绍在广告行业中自然语言处理和推荐系统实践。本篇主要介绍使用chatgpt类LLM进行数据标注任务并蒸馏到生产小模型&#xff0c;对于希望使用chatgpt类LLM进行打标并部署到生产任务中的小伙伴可…

JVM的垃圾收集算法

1.算法的分类 1.1标记清除算法 第一步&#xff1a;标记&#xff08;找出内存中需要回收的对象&#xff0c;并且把它们标记出来&#xff09; 根据可达性算法&#xff0c;标记的是存活的对象&#xff0c;然后将其他的空间进行回收 第二步&#xff1a;清除&#xff08;清除掉被…

Sentinel 监控数据持久化(mysql)

Sentinel 实时监控仅存储 5 分钟以内的数据&#xff0c;如果需要持久化&#xff0c;需要通过调用实时监控接口来定制&#xff0c;即自行扩展实现 MetricsRepository 接口&#xff08;修改 控制台源码&#xff09;。 本文通过使用Mysql持久化监控数据。 1.构建存储表&#xff08…

ESP32 Arduino实战Web篇-使用 WebSocket 创建 ESP32 Web 服务器

本文将详细介绍如何使用 WebSocket 创建 ESP32 Web 服务器,解释WebSocket原理与搭建步骤,并附超详细的代码解释 假设我们需要创建一个使用 ESP32 通过 WiFi 控制灯泡的项目。实现非常简单:我们将 ESP32 设置为软 AP 或 STA 模式,使其能够提供一个网页,显示灯开关的状态为…