Qt5.14.2 深入理解Qt多线程编程,掌握线程池架构实现高效并发


在高并发的软件系统中,多线程编程是解决性能瓶颈和提高系统吞吐量的有效手段。作为跨平台的应用程序开发框架,Qt为我们提供了强大的多线程支持。本文将深入探讨Qt多线程编程的实现细节,并介绍线程池的设计思想,帮助读者彻底掌握Qt多线程编程技巧。


一、Qt的两种多线程实现方式剖析

Qt中实现多线程编程主要有两种方式:重写QThread类的run()函数和使用信号与槽。


1、重写QThread的run()函数

这种方式需要继承QThread类并重写虚函数run(),将需要并发执行的代码逻辑放在run()函数中。例如:

class WorkThread : public QThread {
public:void run() override {//并发执行的代码qDebug() << "Current thread:" << QThread::currentThreadId();//执行耗时操作heavyWorkLoad();}
};

在主线程中,我们只需创建WorkThread对象并调用start()即可启动新线程:

WorkThread *worker = new WorkThread;
worker->start();

这种方法的优点是直观简单,缺点是run()函数作为线程执行体只能有一个入口,不太适合处理多个工作单元并发执行的场景。


2、使用信号与槽方式

Qt的信号与槽机制也可以用于实现多线程编程,它的思路是:

(1)、创建QThread对象作为新线程

(2)、创建执行体对象,并使用QObject::moveToThread()将其移动到新线程

(3)、在主线程通过连接信号与槽的方式,间接调用执行体对象的槽函数,从而启动新线程中的任务


在这里插入图片描述

具体代码如下:

//ExecutionBody.h
class ExecutionBody : public QObject {Q_OBJECT
public slots:void execution() {//并发执行的代码  qDebug() << "Executing in thread" << QThread::currentThreadId();heavyWorkLoad();}
};//main.cpp
int main() {QThread *worker = new QThread;ExecutionBody *body = new ExecutionBody;body->moveToThread(worker);QObject::connect(worker, &QThread::started, body, &ExecutionBody::execution);worker->start();return app.exec();
}

相比第一种方法,信号与槽方式支持在新线程中执行多个函数,更加灵活。但也相对复杂一些,开发者需要清晰地理解信号连接、事件循环等概念。


二、突破瓶颈,构建高效线程池

前面介绍了Qt的基本多线程实现方式,不过在实际项目中,如果只是简单地启动固定数量的线程,可能会面临以下问题:

(1)、线程的创建和销毁代价较高

(2)、线程数量太多,会加重系统的线程调度开销

(3)、大量线程空转,造成CPU资源浪费

为了解决这些问题,我们需要引入线程池的概念,将闲置的线程资源统一管理和调度,避免频繁创建和销毁线程。Qt提供了QThreadPool类实现了这一机制。


1、QThreadPool设计原理


QThreadPool内部管理了一组工作线程(工作者线程),当有任务投递时,线程池会将任务分配给空闲的工作线程执行,避免频繁创建和销毁线程。此外,QThreadPool还支持设置活跃线程数上限,在线程全部忙碌时也不会盲目创建新的工作线程,从而避免过度占用系统资源。

QThreadPool采用信号与槽的方式将任务分发给工作线程。具体来说,当我们调用QThreadPool::start()投递任务时,QThreadPool会为任务创建一个QRunnable对象,并通过内部信号连接到某个工作线程,由工作线程执行QRunnable的run()函数。

在这里插入图片描述


2、QThreadPool使用示例

下面通过一个简单的例子展示如何使用QThreadPool:

//WorkerTask.h
class WorkerTask : public QRunnable {
public:void run() override {//执行任务逻辑qDebug() << "Executing task in thread" << QThread::currentThreadId();heavyWorkLoad();}
};//main.cpp 
int main() {QThreadPool *pool = QThreadPool::globalInstance();//设置最大线程数pool->setMaxThreadCount(QThread::idealThreadCount());//投递任务for(int i=0; i<200; ++i) {  WorkerTask *task = new WorkerTask;pool->start(task);}return app.exec();
}

这个示例首先获取全局QThreadPool实例,并设置最大工作线程数为当前系统的理想线程数(通常为CPU核心数)。然后循环构建WorkerTask对象并调用QThreadPool::start()投递,线程池会自动将任务分发给空闲的线程执行。

需要注意的是,QThreadPool默认采用栈内存管理QRunnable对象,也就是说在QRunnable的run()函数执行完毕后,QThreadPool会自动销毁对象。如果我们需要在run()函数执行完毕后继续访问QRunnable对象的数据成员,应该设置QThreadPool的stackSize属性(即将对象放在堆内存分配)。


三、多线程开发中的注意事项

尽管QThreadPool大大简化了多线程编程流程,但在实际开发中,我们仍需注意一些潜在的安全隐患和性能风险:

1、线程间数据访问安全

当多个线程并发访问同一份数据时,很容易出现竞态条件。Qt提供了QMutex、QSemaphore、QReadWriteLock等同步原语类,我们可以利用它们来保护线程间共享数据的完整性。

另外,Qt还提供了QAtomicInteger和QAtomicPointer等原子操作类,能够确保基础数据类型的读写操作的原子性。对于简单的计数、状态位的读写,使用原子操作类可以避免加锁开销。


2、任务队列控制策略

使用QThreadPool虽然能避免频繁创建销毁线程,但如果任务投递过多且执行时间过长,任务队列会持续积压,可能导致响应延迟或内存占用過高。

因此,我们需要对任务队列的长度作出合理控制。QThreadPool提供了两个相关的API:

  • QThreadPool::reserveThread()可以为高优先级任务预留线程资源
  • QThreadPool::setMaxThreadCount()可以动态调整线程池的最大线程数

我们可以在投递任务前检查当前队列长度,对于优先级较高的任务使用reserveThread()保留资源,对于优先级较低的任务可以选择延迟投递或动态增加线程池大小。


3、避免死锁

在多线程编程中,如果多个线程互相持有对方所需要的锁资源,就会发生死锁。例如下面的代码:

QMutex mutex1, mutex2;//线程1
mutex1.lock(); 
...
mutex2.lock(); //阻塞//线程2  
mutex2.lock();
...
mutex1.lock(); //阻塞

避免死锁的一个常用策略是:对所有需要加锁的代码采用统一的加锁顺序,每个线程按相同顺序申请锁。


4、减少线程切换开销

线程切换是一个非常耗时的操作,会带来较大的性能开销。我们应该尽量减少线程切换的发生,例如:

  • 将密集计算的代码块集中在一个或几个线程中,避免在多个线程间切换
  • 避免线程中的循环中阻塞操作(如休眠、加锁等),这会使该线程长时间占用CPU
  • 采用无锁编程,利用原子操作和内存屏障指令实现线程安全操作

通过本文的介绍,希望你能够加深对Qt多线程编程的理解,在实际开发中合理使用多线程,提高应用程序的整体性能。下一篇文章将为你带来更多实战案例,进一步展示Qt多线程编程的实践技巧。

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

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

相关文章

网盘聚合工具:统筹管理所有网盘资源 | 开源日报 No.203

alist-org/alist Stars: 35.6k License: AGPL-3.0 alist 是一个支持多存储的文件列表/WebDAV 程序&#xff0c;使用 Gin 和 Solidjs。 该项目的主要功能、关键特性、核心优势包括&#xff1a; 支持多种存储方式易于部署和开箱即用文件预览&#xff08;PDF、markdown、代码等&…

SpringBoot2.7集成Swagger3

Swagger2已经在17年停止维护了&#xff0c;取而代之的是 Swagger3&#xff08;基于openApi3&#xff09;&#xff0c;所以新项目要尽量使用Swagger3. Open API OpenApi是业界真正的 api 文档标准&#xff0c;其是由 Swagger 来维护的&#xff0c;并被linux列为api标准&#x…

Web基础06-AJAX,Axios,JSON数据

目录 一、AJAX 1.概述 2.主要作用 3.快速入门 4.AJAX的优缺点 &#xff08;1&#xff09;优点 &#xff08;2&#xff09;缺点 5.同源策略 二、Axios 1.概述 2.快速入门 3.请求方式别名 三、JSON 1.概述 2.主要作用 3.基础语法 4.JSON数据转换 &#xff08;1…

Windows11安装Msql8.0版本详细安装步骤!

文章目录 前言一、下载Mysql二、安装Mysql三、登录验证三、环境变量配置总结 前言 每次搭建新环境的时候&#xff0c;都需要网上搜寻安装的步骤教程&#xff01;为了以后方便查阅&#xff01;那么本次就记录一下Windows11安装Msql8.0的详细步骤&#xff01;也希望能帮助到有需…

抖音无水印视频关键词批量下载|视频下载工具

抖音无水印视频关键词批量下载操作说明 我们根据自己的需要开发了抖音视频批量下载工具&#xff0c;现在市面上的视频无水印工具只能通过单个视频链接进行提取&#xff0c;太不方便 所以我们延伸出了 不仅可以通过单个视频链接进行提取也可通过关键词进行视频搜索 进行批量和有…

Python基于深度学习的中文情感分析系统,附源码

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

计算机设计大赛 题目:基于深度学习的中文汉字识别 - 深度学习 卷积神经网络 机器视觉 OCR

文章目录 0 简介1 数据集合2 网络构建3 模型训练4 模型性能评估5 文字预测6 最后 0 简介 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 基于深度学习的中文汉字识别 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0c;学长非常推荐&#xff01; &a…

【AAAI 2024】MuLTI:高效视频与语言理解

一、背景 1.1 多模态的发展 多模态理解模型具有广泛的应用&#xff0c;比如多标签分类&#xff08;Classification&#xff09;、视频问答&#xff08;videoQA&#xff09;和文本视频检索&#xff08;Retrieval&#xff09;等。现有的方法已经在视频和语言理解方面取得了重大…

unity发布安卓获取读取权限

一、Player Settings 设置 Player Settings>Player>Other Settings> Android > Write Permission > External (SDCard). 二、代码 using System.Collections; using System.Collections.Generic; using System.IO; using UnityEngine; using UnityEngine.Andr…

【c++】内联-引用-重载

主页&#xff1a;醋溜马桶圈-CSDN博客 专栏&#xff1a;c_醋溜马桶圈的博客-CSDN博客 gitee&#xff1a;mnxcc (mnxcc) - Gitee.com 目录 1.【c】内联函数 1.1 背景 1.2 内联函数的概念 1.3 内联函数的特性 1.4 宏和内联的小知识 宏的优缺点&#xff1f; C有哪些技术替代…

mac npm install 很慢或报错

npm ERR! code CERT_HAS_EXPIRED npm ERR! errno CERT_HAS_EXPIRED npm ERR! request to https://registry.npm.taobao.org/pnpm failed, reason: certificate has expired 1、取消ssl验证&#xff1a; npm config set strict-ssl false 修改后一般就可以了&#xff0c;…

kingbase 服务器配置(参数修改)

引言&#xff1a; 人大金仓作为国产数据库的佼佼者(单机)&#xff0c;也是每位数据库从业者必须数据库之一 配置文件 kingbase 参数配置 主要由 kingbase.conf 和 kingbase.auto.conf 设置 kingbase.conf 该参数文件为主配置文件&#xff0c;一般情况下&#xff0c;需要 重启…

day03vue学习

day03 一、今日目标 1.生命周期 生命周期介绍生命周期的四个阶段生命周期钩子声明周期案例 2.综合案例-小黑记账清单 列表渲染添加/删除饼图渲染 3.工程化开发入门 工程化开发和脚手架项目运行流程组件化组件注册 4.综合案例-小兔仙首页 拆分模块-局部注册结构样式完善…

数据可视化-ECharts Html项目实战(3)

在之前的文章中&#xff0c;我们学习了如何创建堆积折线图&#xff0c;饼图以及较难的瀑布图并更改图标标题。想了解的朋友可以查看这篇文章。同时&#xff0c;希望我的文章能帮助到你&#xff0c;如果觉得我的文章写的不错&#xff0c;请留下你宝贵的点赞&#xff0c;谢谢。 …

【接口防重复提交】⭐️基于RedisLockRegistry 分布式锁管理器实现

目录 前言 思路 实现方式 实践 1.引入相关依赖 2.aop注解 3.切面类代码 4.由于启动时报错找不到对应的RedisLockRegistry bean&#xff0c;选择通过配置类手动注入&#xff0c;配置类代码如下 测试 章末 前言 项目中有个用户根据二维码绑定身份的接口&#xff0c;由于用户在…

【Unity动画】Unity如何导入序列帧动画(GIF)

Unity 不支持GIF动画的直接播放&#xff0c;我们需要使用序列帧的方式 01准备好序列帧 02全部拖到Unity 仓库文件夹中 03全选修改成精灵模式Sprite 2D ,根据需要修改尺寸&#xff0c;点击Apply 04 创建一个空物体 拖动序列上去 然后全选所有序列帧&#xff0c;拖到这个空物体…

SpringBoot中使用验证码easy-captcha

easy-captcha使用的大概逻辑: 当一个请求发送到后端服务器请求验证,服务器使用easy-captcha生成一个验证码图片,并通过session将验证信息保存在服务器,当用户登录校验时候,会从ession中取出对比是否一致 但是前后端分离之后 由于跨域问题 以上就无法实现了 下面这种情况没…

完整指南:如何使用 Stable Diffusion API

Stable Diffusion 是一个先进的深度学习模型&#xff0c;用于创造和修改图像。这个模型能够基于文本描述来生成图像&#xff0c;让机器理解和实现用户的创意。使用这项技术的关键在于掌握其 API&#xff0c;通过编程来操控图像生成的过程。 在探索 Stable Diffusion API 的世界…

Linux进程通信补充——System V通信

三、System V进程通信 ​ System V是一个单独设计的内核模块&#xff1b; ​ 这套标准的设计不符合Linux下一切皆文件的思想&#xff0c;尽管隶属于文件部分&#xff0c;但是已经是一个独立的模块&#xff0c;并且shmid与文件描述符之间的兼容性做的并不好&#xff0c;网络通…

【K8S】docker和K8S(kubernetes)理解?docker是什么?K8S架构、Master节点 Node节点 K8S架构图

docker和K8S理解 一、docker的问世虚拟机是什么&#xff1f;Docker的问世&#xff1f;docker优点及理解 二、Kubernetes-K8SK8S是什么&#xff1f;简单了解K8S架构Master节点Node节点K8S架构图 一、docker的问世 在LXC(Linux container)Linux容器虚拟技术出现之前&#xff0c;业…