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

news2024/7/6 17:51:53

一、前言

        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.coloradmin.cn/o/1233768.html

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

相关文章

C#使用MaxMind.GeoIP2数据库查询当前ip地址

GeoLite2-City.mmdb下载 因为比较简单&#xff0c;直接上代码&#xff0c;代码展示获取ip地址的国家和城市信息 using MaxMind.GeoIP2; using MaxMind.GeoIP2.Model; using System; using System.Collections; using System.Collections.Generic; using System.Linq; using Sy…

利用 Gem5 模拟器创建一个简单的配置脚本——翻译自官网

文章目录 创建简单的配置脚本gem5 配置脚本关于模拟对象的插话 创建配置文件全系统与系统调用模拟 运行Gem5 创建简单的配置脚本 本章教程将指导你如何为 gem5 设置一个简单的模拟脚本&#xff0c;并首次运行 gem5。我们假定你已完成本教程第一章的学习&#xff0c;并已成功创…

DITTEL控制器维修SENSITRON6-2AE

DITTEL工控产品维修包括&#xff1a;德国DITTEL平衡测试仪维修,DITTEL模块&#xff0c;过程监控模块&#xff0c;DITTEL控制器&#xff0c;平衡头&#xff0c;机电平衡头&#xff0c;显示器&#xff0c;平衡系统等产品。 DITTEL过程控制模块维修 DM6000是一个过程控制模块&…

第1关:图的邻接表存储及求邻接点操作

任务要求参考答案评论2 任务描述相关知识编程要求测试说明 任务描述 本关任务&#xff1a;要求从文件输入顶点和边数据&#xff0c;包括顶点信息、边、权值等&#xff0c;编写程序实现以下功能。 1&#xff09;构造图G的邻接表和顶点集&#xff0c;即图的存储结构为邻接表。 …

使用wxPython和PyMuPDF合并PDF文档并自动复制到剪贴板

导语&#xff1a;处理大量的PDF文档可能会变得复杂和耗时。但是&#xff0c;使用Python编程和一些强大的库&#xff0c;如wxPython和PyMuPDF&#xff0c;可以使这个任务变得简单而高效。本文将详细解释一个示例代码&#xff0c;展示如何使用这些库来创建一个可以选择文件夹中的…

STM32 -Bin/Hex文件格式解析

文章目录 1. 概述2. Hex文件2.1 格式解析2.2 数据类型2.3 举例解析2.4 合并两个Hex文件方法 3 总结&#xff08;未完待续&#xff09; 1. 概述 Hex文件&#xff1a;它是单片机和嵌入式工程编译输出的一种常见的目标文件格式&#xff08;比如keil就能编译输出hex文件&#xff0…

Kubernetes容器状态探测的艺术

在Kubernetes集群中维护容器状态更像是一种艺术&#xff0c;而不是科学。原文: The Art and Science of Probing a Kubernetes Container[1] 在Kubernetes集群中维护容器状态更像是一种艺术&#xff0c;而不是科学。 本文将带你深入理解容器探测[2]&#xff0c;并特别关注相对较…

SQL常见函数整理 —— LAG() 向上偏移

1. 用法 窗口函数&#xff0c;用于访问窗口中当前行之前的行的数据。该函数可以根据需要计算当前行之前的值&#xff0c;使我们能够轻松地比较不同行之间的差异和变化。 2. 基本语法 LAG(column, offset, default_value) OVER (ORDER BY column)column&#xff1a;代表在返回…

代码随想录刷题】Day15 二叉树02------延伸题目练习

文章目录 1.【100】相同的树1.1 题目描述1.2 java代码实现 2.【572】另一棵树的子树2.1 题目描述2.2 java代码实现 【100】相同的树 【572】另一棵树的子树 1.【100】相同的树 1.1 题目描述 给你两棵二叉树的根节点 p 和 q &#xff0c;编写一个函数来检验这两棵树是否相同。…

“轻松管理你的文件库:按大小归类保存,高效整理!“

亲爱的朋友们&#xff0c;你是否曾经为了整理电脑中杂乱无章的文件而感到烦恼&#xff1f;文件大小不一&#xff0c;无法快速找到所需内容&#xff0c;实在让人感到心力交瘁。但现在&#xff0c;我们为你带来一种全新的解决方案&#xff0c;让你的文件管理更轻松&#xff0c;更…

算法设计与分析复习--回溯(一)

文章目录 上一篇回溯法性质子集和问题装载问题0-1背包问题下一篇 上一篇 算法设计与分析复习–贪心&#xff08;二&#xff09; 回溯法性质 类似穷举的搜索尝试过程&#xff0c;在搜索尝试过程中寻找问题的解&#xff0c;组织得井井有条&#xff08;避免遗漏&#xff09;&am…

工业领域的设备“监测”和“检测”有何区别?

在工业领域中&#xff0c;设备的监测和检测是关键的运维活动&#xff0c;它们在保障设备可靠性和生产效率方面发挥着重要作用。尽管这两个术语经常被人们混为一谈&#xff0c;但它们在含义和应用上存在一些关键区别。 "监测"与"检测"的概念 1. 监测&#…

PHP手动为第三方类添加composer自动加载

有时候我们要使用的第三方的类库&#xff08;SDK&#xff09;没用用composer封装好&#xff0c;无法用composer进行安装&#xff0c;怎么办呢&#xff1f;&#xff1f;&#xff1f; 步骤如下&#xff1a; 第一步、下载你需要的SDK文件包&#xff0c;把它放在vendor目录下 第二…

【Python】学习Python面向对象编程的疑问

&#xff08;Java菜鸟来学Python了&#xff09; &#x1f914; 1. 静态方法与类方法什么区别&#xff1f; 实例方法只能被实例对象调用(Python3 中&#xff0c;如果类调用实例方法&#xff0c;需要显示的传self, 也就是实例对象自己)&#xff0c;静态方法(由staticmethod装饰…

【MATLAB源码-第84期】基于matlab的802.11a标准的OFDM系统误码仿真对比QPSK,16QAM。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 基于802.11a标准的OFDM&#xff08;正交频分复用&#xff09;系统是一种高效的无线通信技术&#xff0c;特点如下&#xff1a; 频带与信道&#xff1a; 802.11a工作在5 GHz频段&#xff0c;这个频段相对于2.4 GHz&#xff08…

O-Star|再相识

暑去秋来&#xff0c;岁月如梭&#xff0c;几名"O-Star"们已经入职一段时间&#xff0c;在这期间他们褪去青涩&#xff0c;逐渐适应了公司的工作环境和文化&#xff0c;迈向沉稳&#xff5e; 为了进一步加深校招生之间的交流与了解&#xff0c;提高校招生的凝聚力和…

77基于matlab的蚁群优化路径算法,二维路径和三维路径优化

基于matlab的蚁群优化路径算法&#xff0c;二维路径和三维路径优化。输出可视化最优路径和距离迭代曲线。数据可更换自己的&#xff0c;程序已调通&#xff0c;可直接运行。 77三维和二维路径可视化 (xiaohongshu.com)

Docker快速安装Mariadb11.1

MariaDB数据库管理系统是MySQL的一个分支&#xff0c;主要由开源社区在维护&#xff0c;采用GPL授权许可 MariaDB的目的是完全兼容MySQL&#xff0c;包括API和命令行&#xff0c;使之能轻松成为MySQL的代替品。在存储引擎方面&#xff0c;使用XtraDB来代替MySQL的InnoDB。 Mari…

第2关:图的深度优先遍历

任务要求参考答案评论2 任务描述相关知识编程要求测试说明 任务描述 本关任务&#xff1a;以邻接矩阵存储图&#xff0c;要求编写程序实现图的深度优先遍历。 相关知识 图的深度优先遍历类似于树的先序遍历, 是树的先序遍历的推广&#xff0c;其基本思想如下&#xff1a; …