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);
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

智能座舱架构与芯片- (11) 软件篇 上

一、智能汽车基础软件平台分类 汽车软件主要分为应用软件和基础软件。应用软件和业务形态高度关联&#xff0c;不同控制器的应用软件之间差异较大。基础软件介于应用软件和硬件之间&#xff0c;用于屏蔽硬件特性、支撑应用软件。可有效地实现应用软件与硬件之间解耦&#xff0…

Kubernetes容器状态探测的艺术

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

C++ LibCurl实现Web隐藏目录扫描

LibCurl是一个开源的免费的多协议数据传输开源库&#xff0c;该框架具备跨平台性&#xff0c;开源免费&#xff0c;并提供了包括HTTP、FTP、SMTP、POP3等协议的功能&#xff0c;使用libcurl可以方便地进行网络数据传输操作&#xff0c;如发送HTTP请求、下载文件、发送电子邮件等…

Stable Diffusion XL网络结构-超详细原创

强烈推荐先看本人的这篇 Stable Diffusion1.5网络结构-超详细原创-CSDN博客 1 Unet 1.1 详细整体结构 1.2 缩小版整体结构 以生成图像1024x1024为例&#xff0c;与SD1.5的3个CrossAttnDownBlock2D和CrossAttnUpBlock2D相比&#xff0c;SDXL只有2个&#xff0c;但SDXL的Cros…

如何选择示波器?

简介 对于很多工程师来讲&#xff0c;从市场中上百款不同价格和规格的各种型号的示波器中&#xff0c;选择一台新示波器是一件很挠首的事情。本文就旨在指引你拨开迷雾&#xff0c;希望能帮助你避免付出昂贵的代价。 重中之重 选择示波器的第一步不是要看那些示波器的广告和规…

MAVEN——PACKAGE、INSTALL、DEPLOY的联系与区别

我们在用maven构建java项目时&#xff0c;最常用的打包命令有mvn package、mvn install、deploy&#xff0c;这三个命令都可完成打jar包或war&#xff08;当然也可以是其它形式的包&#xff09;的功能&#xff0c;但这三个命令还是有区别的。下面通过分别执行这三个命令的输出结…

Openlayer【三】—— 绘制多边形GeoJson边界绘制

1.1、绘制多边形 在绘制多边形和前面绘制线有异曲同工之妙&#xff0c;多边形本质上就是由多个点组成的线然后连接组成的面&#xff0c;这个面就是最终的结果&#xff0c;那么这里使用到的是Polygon对象&#xff0c;而传给这个对象的值也是多个坐标&#xff0c;坐标会一个个的…

分享几个MicroPython开发的ES32项目源码

最近在学习物联网&#xff0c;必不可少的就是需要玩一下ESP8266和ESP32&#xff0c;当然开发它们的语言分为C/C 今天带给大家几个MicroPython开发的几个ESP32的项目源码&#xff0c;喜欢的童鞋可以关注一下 1、点亮开发板LED灯 from machine import Pinled_pin Pin(4,Pin.O…

软件测评中心进行安全测试有哪些流程?安全测试报告如何收费?

在当今数字化时代&#xff0c;软件安全测试是每个软件开发团队都不能忽视的重要环节。安全测试是指对软件产品进行系统、全面的安全性评测与检测的过程。它旨在发现并修复软件中存在的漏洞和安全隐患&#xff0c;以确保软件能够在使用过程中保护用户的数据和隐私不被非法访问和…

SpringSecurity+JWT权限认证

SpringSecurity默认的是采用Session来判断请求的用户是否登录的&#xff0c;但是不方便分布式的扩展 虽然SpringSecurity也支持采用SpringSession来管理分布式下的用户状态&#xff0c;不过现在分布式的还是无状态的Jwt比较主流 一、创建SpringBoot的项目 spring-boot-starte…

【giszz笔记】产品设计标准流程【8】

&#xff08;续上回&#xff09; 真的没想到写了8个章节&#xff0c;想参考之前文章的&#xff0c;我把链接给到这里。 【giszz笔记】产品设计标准流程【7】-CSDN博客 【giszz笔记】产品设计标准流程【6】-CSDN博客 【giszz笔记】产品设计标准流程【5】-CSDN博客 【giszz笔…

Transformer——encoder

本文参考了b站的Eve的科学频道中的深入浅出解释Transformer原理和DASOU讲AI中的Transformer从零详解。 入浅出解释Transformer原理 Transformer从零详解 前言&#xff1a; 在自然语言识别中&#xff0c;之前讲过lstm&#xff0c;但是lstm有明显的缺陷&#xff0c;就是当文本过…

[SCTF 2021]rceme

文章目录 前置知识可变参数绕过create_function注入无字母数字RCE动态链接库so绕过disable_functions利用php原生类进行文件读取 解题过程 前置知识 可变参数绕过 PHP 在用户自定义函数中支持可变数量的参数列表。在 PHP 5.6 及以上的版本中&#xff0c;由 … 语法实现&#x…

redis的过期策略以及定时器的实现

Redis是客户端服务器结构的程序&#xff0c;客户端与服务器通过网络通信&#xff0c;所以对于keys *这种的操作在大型企业中不太建议&#xff0c;生产环境下的key会非常多&#xff0c;Redis是但现成的服务器&#xff0c;执行keys*的时间非常长&#xff0c;就会导致redis服务器阻…

同为科技(TOWE)桌面PDU插排:一款可以DIY定制的“超级插座”

当今社会&#xff0c;各种电子产品和家用电器已成为人们日常生活中不可或缺的一部分&#xff0c;在带给人们便利的同时&#xff0c;也使得电力使用变得更加频繁和重要。然而&#xff0c;当前市面上很多普通插座由于功能单一、材质粗劣、插口数量受限、充电速度过慢、插头间互相…

子虔与罗克韦尔自动化合作 进博会签约自动化净零智造联创中心

11月6日进博会现场&#xff0c;漕河泾罗克韦尔自动化净零智造联创中心合作协议签约暨合作伙伴&#xff08;第一批&#xff09;授牌仪式举办&#xff0c;子虔科技作为联创中心合作伙伴签约&#xff0c;携手共建智能制造&#xff0c;引领行业可持续发展。 图示&#xff1a;子虔科…

QTableView表头Header增加复选框Checkbox

原文出处&#xff1a;Qt 之 QHeaderView 添加复选框_qtableview添加复选框-CSDN博客 这哥们只贴了部分代码&#xff0c;我还是把它弄好分享给大家吧 DTableHeaderView.h #ifndef DTABLEHEADERVIEW_H #define DTABLEHEADERVIEW_H#include <QHeaderView>class DTableHea…

高精度人像背景分割SDK技术解决方案

图像处理技术已经成为企业和个人生活中不可或缺的一部分&#xff0c;特别是在人像处理方面&#xff0c;如何准确、高效地将人物与背景分离&#xff0c;一直是一个技术难题。然而&#xff0c;美摄科技凭借其在AI深度学习领域的深厚积累&#xff0c;推出了一款高精度的人像背景分…

app抓包-突破【单向证书验证代理检测模拟器检测】

0x00 app的普通抓包配置 1.模拟器开启本地以太网代理&#xff0c;设置端口&#xff0c;bp监听以太网系统端口即可 2.科来协议分析&#xff0c;可以获取模拟器进程的网络通讯信息&#xff0c;目标通讯的ip地址 3.封包监听工具&#xff0c;同科来一致&#xff0c;监听的进程即可…

gitlab

Gitlab 安装git yum安装 [rootgit ~]# yum -y install git编译安装 Git官网 #安装依赖关系 [rootgit ~]# yum install curl-devel expat-devel gettext-devel openssl-devel zlib-devel autoconf gcc perl-ExtUtils-MakeMaker # 编译安装 [rootgit ~]# tar -zxf git-2.0…