Qt学习:Qt 进程和线程之四,线程实际应用

为了让程序尽快响应用户操作,在开发应用程序时经常会使用到线程。对于耗时操作如果不使用线程,UI 界面将会长时间处于停滞状态,这种情况是用户非常不愿意看到的,我们可以用线程来解决这个问题。

大多数情况下,多线程耗时操作会与 UI 进行交互,比如:显示进度、加载等待。。。让用户明确知道目前的状态,并对结果有一个直观的预期,甚至有趣巧妙的设计,能让用户爱上等待,把等待看成一件很美好的事。

一、多线程操作 UI 界面的示例

下面,是一个使用多线程操作 UI 界面的示例 - 更新进度条,采用子类化 QThread 的方式。与此同时,分享在此过程中有可能遇到的问题及解决方法。

首先创建 QtGui 应用,工程名称为 “myThreadBar”,类名选择 “QMainWindow”,其他选项保持默认即可。再添加一个名称为 WorkerThread 的头文件,定义一个 WorkerThread 类,让其继承自 QThread,并重写 run () 函数,修改 workerthread.h 文件如下:

#ifndef WORKERTHREAD_H
#define WORKERTHREAD_H#include <QThread>
#include <QDebug>class WorkerThread : public QThread
{Q_OBJECTpublic:explicit WorkerThread(QObject *parent = 0): QThread(parent){qDebug() << "Worker Thread : " << QThread::currentThreadId();}protected:virtual void run() Q_DECL_OVERRIDE{qDebug() << "Worker Run Thread : " << QThread::currentThreadId();int nValue = 0;while (nValue < 100){// 休眠50毫秒msleep(50);++nValue;// 准备更新emit resultReady(nValue);}}signals:void resultReady(int value);
};#endif // WORKERTHREAD_H

通过在 run () 函数中调用 msleep (50),线程会每隔 50 毫秒让当前的进度值加 1,然后发射一个 resultReady () 信号,其余时间什么都不做。在这段空闲时间,线程不占用任何的系统资源。当休眠时间结束,线程就会获得 CPU 时钟,将继续执行它的指令。在 mainwindow.ui 上添加一个按钮和进度条部件,然后 mainwindow.h 修改如下:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H#include <QMainWindow>
#include "workerthread.h"namespace Ui {
class MainWindow;
}class MainWindow : public QMainWindow
{Q_OBJECTpublic:explicit MainWindow(QWidget *parent = nullptr);~MainWindow();private slots:// 更新进度void handleResults(int value);// 开启线程void startThread();private:Ui::MainWindow *ui;WorkerThread m_workerThread;
};#endif // MAINWINDOW_H

然后 mainwindow.cpp 修改如下:

#include "mainwindow.h"
#include "ui_mainwindow.h"MainWindow::MainWindow(QWidget *parent) :QMainWindow(parent),ui(new Ui::MainWindow)
{ui->setupUi(this);qDebug() << "Main Thread : " << QThread::currentThreadId();        // 连接信号槽this->connect(ui->pushButton, SIGNAL(clicked(bool)), this, SLOT(startThread()));
}MainWindow::~MainWindow()
{delete ui;
}void MainWindow::handleResults(int value)
{qDebug() << "Handle Thread : " << QThread::currentThreadId();ui->progressBar->setValue(value);
}void MainWindow::startThread()
{WorkerThread *workerThread = new WorkerThread(this);this->connect(workerThread, SIGNAL(resultReady(int)), this, SLOT(handleResults(int)));// 线程结束后,自动销毁this->connect(workerThread, SIGNAL(finished()), workerThread, SLOT(deleteLater()));workerThread->start();
}

由于信号与槽连接类型默认为 “Qt::AutoConnection”,在这里相当于 “Qt::QueuedConnection”。也就是说,槽函数在接收者的线程(主线程)中执行。

执行程序,“应用程序输出” 窗口输出如下:

Main Thread :  0x3140
Worker Thread :  0x3140
Worker Run Thread :  0x2588
Handle Thread :  0x3140

显然,UI 界面、Worker 构造函数、槽函数处于同一线程(主线程),而 run () 函数处于另一线程(次线程)。

回到顶部

二、避免多次 connect

当多次点击 “开始” 按钮的时候,就会多次 connect (),从而启动多个线程,同时更新进度条。为了避免这个问题,我们先在 mainwindow.h 上添加私有成员变量 "WorkerThread m_workerThread;",然后修改 mainwindow.cpp 如下:

#include "mainwindow.h"
#include "ui_mainwindow.h"MainWindow::MainWindow(QWidget *parent) :QMainWindow(parent),ui(new Ui::MainWindow)
{ui->setupUi(this);// 连接信号槽this->connect(ui->pushButton, SIGNAL(clicked(bool)), this, SLOT(startThread()));this->connect(&m_workerThread, SIGNAL(resultReady(int)), this, SLOT(handleResults(int)));
}MainWindow::~MainWindow()
{delete ui;
}void MainWindow::handleResults(int value)
{qDebug() << "Handle Thread : " << QThread::currentThreadId();ui->progressBar->setValue(value);
}void MainWindow::startThread()
{if (!m_workerThread.isRunning())m_workerThread.start();
}

不再在 startThread () 函数内创建 WorkerThread 对象指针,而是定义私有成员变量,再将 connect 添加在构造函数中,保证了信号槽的正常连接。在线程 start () 之前,可以使用 isFinished () 和 isRunning () 来查询线程的状态,判断线程是否正在运行,以确保线程的正常启动。

三、优雅地结束线程的两种方法

如果一个线程运行完成,就会结束。可很多情况并非这么简单,由于某种特殊原因,当线程还未执行完时,我们就想中止它。

不恰当的中止往往会引起一些未知错误。比如:当关闭主界面的时候,很有可能次线程正在运行,这时,就会出现如下提示:

QThread: Destroyed while thread is still running

这是因为次线程还在运行,就结束了 UI 主线程,导致事件循环结束。这个问题在使用线程的过程中经常遇到,尤其是耗时操作。大多数情况下,当程序退出时,次线程也许会正常退出。这时,虽然抱着侥幸心理,但隐患依然存在,也许在极少数情况下,就会出现 Crash。

所以,我们应该采取合理的措施来优雅地结束线程,一般思路:

  1. 发起线程退出操作,调用 quit () 或 exit ()。

  2. 等待线程完全停止,删除创建在堆上的对象。

  3. 适当的使用 wait ()(用于等待线程的退出)和合理的算法。

方法一

这种方式是 Qt4.x 中比较常用的,主要是利用 “QMutex 互斥锁 + bool 成员变量” 的方式来保证共享数据的安全性。在 workerthread.h 上继续添加互斥锁、析构函数和 stop () 函数,修改如下:

#ifndef WORKERTHREAD_H
#define WORKERTHREAD_H#include <QThread>
#include <QMutexLocker>
#include <QDebug>class WorkerThread : public QThread
{Q_OBJECTpublic:explicit WorkerThread(QObject *parent = 0): QThread(parent),m_bStopped(false){qDebug() << "Worker Thread : " << QThread::currentThreadId();}~WorkerThread(){stop();quit();wait();}void stop(){qDebug() << "Worker Stop Thread : " << QThread::currentThreadId();QMutexLocker locker(&m_mutex);m_bStopped = true;}protected:virtual void run() Q_DECL_OVERRIDE {qDebug() << "Worker Run Thread : " << QThread::currentThreadId();int nValue = 0;while (nValue < 100){// 休眠50毫秒msleep(50);++nValue;// 准备更新emit resultReady(nValue);// 检测是否停止{QMutexLocker locker(&m_mutex);if (m_bStopped)break;}// locker超出范围并释放互斥锁}}signals:void resultReady(int value);private:bool m_bStopped;QMutex m_mutex;
};#endif // WORKERTHREAD_H

当主窗口被关闭,其 “子对象” WorkerThread 也会析构调用 stop () 函数,使 m_bStopped 变为 true,则 break 跳出循环结束 run () 函数,结束进程。当主线程调用 stop () 更新 m_bStopped 的时候,run () 函数也极有可能正在访问它(这时,他们处于不同的线程),所以存在资源竞争,因此需要加锁,保证共享数据的安全性。

为什么要加锁?很简单,是为了共享数据段操作的互斥。避免形成资源竞争的情况(多个线程有可能访问同一共享资源的情况)。

方法二

Qt5 以后,可以使用 requestInterruption ()、isInterruptionRequested () 这两个函数,使用很方便,修改 workerthread.h 文件如下:

#ifndef WORKERTHREAD_H
#define WORKERTHREAD_H#include <QThread>
#include <QMutexLocker>
#include <QDebug>class WorkerThread : public QThread
{Q_OBJECTpublic:explicit WorkerThread(QObject *parent = nullptr): QThread(parent){qDebug() << "Worker Thread : " << QThread::currentThreadId();}~WorkerThread(){// 请求终止requestInterruption();quit();wait();}protected:virtual void run() Q_DECL_OVERRIDE{qDebug() << "Worker Run Thread : " << QThread::currentThreadId();int nValue = 0;// 是否请求终止while (!isInterruptionRequested()){while (nValue < 100){// 休眠50毫秒msleep(50);++nValue;// 准备更新emit resultReady(nValue);}}}
signals:void resultReady(int value);
};#endif // WORKERTHREAD_H

在耗时操作中使用 isInterruptionRequested () 来判断是否请求终止线程,如果没有,则一直运行;当希望终止线程的时候,调用 requestInterruption () 即可。这两个函数内部也使用了互斥锁 QMutex。

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

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

相关文章

离线数仓-项目介绍

1. 系统架构 2. 介绍流程 公司的困难数据的来源 业务日志 Flume采集日志数据 选型 ETL flume内存不够&#xff0c;通过ganglia监控器发现 提高吞吐量&#xff0c;batchSize kafka 高效读写 提高吞吐量 kafka挂了 kafka丢数问题 数据重复问题 数据乱序问题 消费策略…

深度学习部署:FastDeploy部署教程(CSharp版本)

FastDeploy部署教程(CSharp版本) 1. FastDeploy介绍 FastDeploy是一款全场景、易用灵活、极致高效的AI推理部署工具&#xff0c; 支持云边端部署。提供超过 &#x1f525;160 Text&#xff0c;Vision&#xff0c; Speech和跨模态模型&#x1f4e6;开箱即用的部署体验&#xf…

F. Dasha and Nightmares

Dasha, an excellent student, is studying at the best mathematical lyceum in the country. Recently, a mysterious stranger brought nn words consisting of small latin letters s1,s2,…,sns1,s2,…,sn to the lyceum. Since that day, Dasha has been tormented by ni…

消息队列常见问题(1)-如何保障不丢消息

目录 1. 为什么消息队列会丢消息&#xff1f; 2. 怎么保障消息可靠传递&#xff1f; 2.1 生产者不丢消息 2.2 服务端不丢消息 2.3 消费者不丢消息 3. 消息丢失如何快速止损&#xff1f; 3.1 完善监控 3.2 完善止损工具 1. 为什么消息队列会丢消息&#xff1f; 现在主流…

支付模块功能实现(小兔鲜儿)【Vue3】

支付 渲染基础数据 支付页有俩个关键数据&#xff0c;一个是要支付的钱数&#xff0c;一个是倒计时数据&#xff08;超时不支付商品释放&#xff09; 准备接口 import request from /utils/httpexport const getOrderAPI (id) > {return request({url: /member/order/$…

系列四、IOC操作Bean管理(FactoryBean)

一、概述 Spring中有2种类型的Bean&#xff0c;一种是普通Bean&#xff0c;另外一种是工厂Bean&#xff08;FactoryBean&#xff09;&#xff1b;普通Bean&#xff1a;在配置文件中定义的Bean的类型就是返回类型&#xff1b;工厂Bean&#xff1a;在配置文件中定义的Bean的类型…

目标检测YOLO实战应用案例100讲-基于深度学习的红外小目标检测关键算法研究

目录 基于深度学习的红外小目标检测方法研究 传统的红外小目标检测方法

PyTorch深度学习实战(10)——过拟合及其解决方法

PyTorch深度学习实战&#xff08;10&#xff09;——过拟合及其解决方法 0. 前言1. 过拟合基本概念2. 添加 Dropout 解决过拟合3. 使用正则化解决过拟合3.1 L1 正则化3.2 L2 正则化 4. 学习率衰减小结系列链接 0. 前言 过拟合 (Overfitting) 是指在机器学习中&#xff0c;模型…

android studio内存分析之Memory profiler的使用

目录 Android Studio中内存分析工具Memory profiler的使用1. 打开Memory Profiler2. 工具使用3. 内存选项说明4. 内存性能分析器概览5. 内存计算方式6. 查看内存分配7. 捕获java/kotlin方式查看内存分配8. 堆转储文件导入和导出 内存性能分析器中的泄漏检测 Android Studio中内…

刚性配准与非刚性配准

前言 “配准”这个词其实应用的场景很多&#xff0c;例如在AR设备上进行定位需要用到的图像配准&#xff0c;需要提前存储图像的特征信息&#xff0c;然后用AR设备的摄像头实时计算图像特征并进行匹配&#xff0c;配准成功后进行跟踪。 本文所说的“配准”&#xff0c;是应用…

日期选择器多选换行

<el-form-item label"日期选择"><div class"multi-date-picker"><div class"date-item"><span class"dateIcon"><el-icon><Calendar /></el-icon></span><span class"dateIt…

【ArcGIS Pro二次开发】(58):数据的本地化存储

在做村规工具的过程中&#xff0c;需要设置一些参数&#xff0c;比如说导图的DPI&#xff0c;需要导出的图名等等。 每次导图前都需要设置参数&#xff0c;虽然有默认值&#xff0c;但还是需要不时的修改。 在使用的过程中&#xff0c;可能会有一些常用的参数&#xff0c;希望…

Sentinel 2.0 微服务零信任的探索与实践

作者&#xff1a;涯客、十眠 从古典朴素的安全哲学谈起 网络安全现状 现在最常见的企业网络安全架构便是在企业网络边界处做安全防护&#xff0c;而在企业网络内部不做安全防范。这确实为企业的安全建设省了成本也为企业提供了一定的防护能力。但是这类比于现实情况的一个小…

单通道 6GSPS 16位采样DAC子卡模块--【资料下载】

FMC147是一款单通道6.4GSPS&#xff08;或者配置成2通道3.2GSPS&#xff09;采样率的12位AD采集、单通道6GSPS&#xff08;或配置成2通道3GSPS&#xff09;采样率16位DA输出子卡模块&#xff0c;该板卡为FMC标准&#xff0c;符合VITA57.4规范&#xff0c;该模块可以作为一个理想…

力扣:54. 螺旋矩阵(Python3)

题目&#xff1a; 给你一个 m 行 n 列的矩阵 matrix &#xff0c;请按照 顺时针螺旋顺序 &#xff0c;返回矩阵中的所有元素。 来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 链接&#xff1a;力扣 示例&#xff1a; 示例 1&#xff1a; 输入&#xff1a;matrix [[1,…

手机便签内容不见了怎么恢复正常?

在日常生活和工作中&#xff0c;很多人都需要随手记录事情&#xff0c;例如家庭琐事、孩子相关的事情、指定时间需要完成的工作任务、会议安排等。当我们需要随时随地记录事情的时候&#xff0c;手机便签应用就是非常不多的选择&#xff0c;我们直接打开手机上的便签APP就可以新…

安全基础 --- https详解 + 数组(js)

CIA三属性&#xff1a;完整性&#xff08;Confidentiality&#xff09;、保密性&#xff08;Integrity&#xff09;、可用性&#xff08;Availability&#xff09;&#xff0c;也称信息安全三要素。 https 核心技术&#xff1a;用非对称加密传输对称加密的密钥&#xff0c;然后…

【多线程】synchronized 原理

1. 写在前面 本章节主要介绍 synchronized 的一些内部优化机制&#xff0c;这些机制存在的目的呢就是让 synchronized 这把锁更高效更好用&#xff01; 2. 锁升级/锁膨胀 JVM 将 synchronized 锁分为以下四种状态&#xff1a; 无锁&#xff0c;偏向锁&#xff0c;轻量级锁&…

服务器测试之GPU shoc-master测试

精简版指导 lspci | grep -i nvidia lspci -s 4f:00.0 -vvv 适用版本 cuda_11.8.0_520.61.05_linux.run cuda-samples-11.8.tar.gz NVIDIA-Linux-x86_64-525.116.04.run 安装&#xff1a; ./NVIDIA-Linux-x86_64-525.116.04.run 查看是否为一拖八&#xff1a;nvidia-smi topo …

算法通关村第四关——最大栈问题解析

力扣716&#xff0c;设计一个最大栈数据结构&#xff0c;既支持栈操作&#xff0c;又支持查找栈中最大元素。 分析&#xff1a; 在最大栈的问题上&#xff0c;除了实现普通栈拥有的方法pop、push、top外&#xff0c;还需要实现getMax方法来找到当前栈里的最大值。为了在最短事件…