深入理解Qt多线程编程(QThreadPool)
多线程编程在现代软件开发中变得越来越重要,它能够提高应用程序的响应速度和处理性能。在Qt框架中,QThreadPool作为线程池管理工具,被频繁的使用。
目录
概述
接口介绍
底层原理解析
使用方法
概述
QThreadPool是Qt提供的一个线程池实现,用于管理和复用线程。线程池通过复用现有的线程来避免频繁创建和销毁线程带来的性能开销,适用于需要频繁执行并发任务的场景。QThreadPool内部维护了一个线程队列,可以在程序运行时动态分配和管理线程资源。
接口介绍
int activeThreadCount() const
返回当前活动(正在运行任务)线程的数量。这个数字不包括闲置等待工作的线程。
void clear()
清除线程池的任务队列。已经开始的任务将会继续完成,但队列中等待开始的任务会被取消。
bool contains(const QThread *thread) const
检查指定的线程是否属于该线程池。
int expiryTimeout() const和void setExpiryTimeout(int expiryTimeout)
返回/设置线程过期时间,单位是毫秒。
线程在完成任务后,如果在过期时间内没有心的任务分配给它,那么它将被销毁。
默认expiryTimeout值为30000毫秒(30秒)。
注意:
设置expiryTimeout对已运行的线程没有影响。只有新创建的线程才会使用新的expiryTimeout。我们建议在创建线程池之后,调用start()之前设置expiryTimeout。
int maxThreadCount() const和void setMaxThreadCount(int maxThreadCount)
返回/设置线程池中允许的最大线程数。
注意:
maxThreadCount不能为零和负数,线程池至少有一个线程。
void releaseThread()和void reserveThread()
reserveThread()方法的字面意思是保留线程(或者是预留线程),这里的保留线程不是指保留线程池中的线程不被销毁。该方法就是增加activeThreadCount()返回值(活动线程的数量)。reserveThread()提供了一种机制,使得开发者可以更精细地控制线程的并行度和资源占用。通过通知线程池外部线程的存在,它帮助线程池避免在CPU资源已经被占用时过渡创建或激活线程,从而优化了资源的使用和任务的执行效率。
假设最理想的线程数量是10,线程池的
maxThreadCount设置为10,但在线程池外还有1个GUI线程、4个独立任务线程,当线程池满负荷工作时,程序的线程数量将达到15个线程,超出了合理的线程数,导致一系列的性能问题。所以,为了更精细地控制线程数量,当线程池外的线程都工作时,调用线程池的reserveThread()来占用线程,使线程池只能有5个线程同时工作。
如果所有线程都在工作,调用该方法后会导致activeThreadCount()暂时大于maxThreadCount()。releaseThread()方法是释放之前通过reserveThread()方法预留的线程。
uint stackSize() const和void setStackSize(uint stackSize)
获取/设置线程池工作线程的堆栈大小。
默认值为0,即工作线程的堆栈大小使用操作系统默认的堆栈大小。
注意:
设置堆栈大小时只对后续新创建的线程有用,对已创建或正在运行的线程没有影响。
void start(QRunnable *runnable, int priority = 0)
从线程池中拿出一个空闲的线程来运行runnable,如果当前线程池没有空闲的线程,那么runnable将被添加到运行队列中。
示例:
class MyTask : public QRunnable {
public:void run() override {qDebug() << "QRunnable Task is running in thread" << QThread::currentThread();// 模拟任务处理QThread::sleep(2);qDebug() << "QRunnable Task completed in thread" << QThread::currentThread();}
};int main(int argc, char *argv[]) {QCoreApplication app(argc, argv);QThreadPool *threadPool = QThreadPool::globalInstance();threadPool->setMaxThreadCount(3); // 设置最大线程数for (int i = 0; i < 5; ++i) {MyTask *task = new MyTask();threadPool->start(task);}threadPool->waitForDone(); // 等待所有任务完成return app.exec();
}
void start(std::function<void()> functionToRun, int priority = 0)
从线程池中拿出一个空闲的线程来运行functionToRun,如果当前线程没有空闲的线程,那么functionToRun将被添加到运行队列中。
示例:
void myFunctionTask() {qDebug() << "std::function Task is running in thread" << QThread::currentThread();// 模拟任务处理QThread::sleep(2);qDebug() << "std::function Task completed in thread" << QThread::currentThread();
}int main(int argc, char *argv[]) {QCoreApplication app(argc, argv);QThreadPool *threadPool = QThreadPool::globalInstance();threadPool->setMaxThreadCount(3); // 设置最大线程数for (int i = 0; i < 5; ++i) {threadPool->start(std::function<void()>(myFunctionTask));}threadPool->waitForDone(); // 等待所有任务完成return app.exec();
}
bool tryStart(QRunnable *runnable)和bool tryStart(std::function<void()> functionToRun)
尝试从线程池中拿出空闲的线程来运行runnable或functionToRun。
如果调用时,没有空闲的线程,则此函数直接返回false;否则,直接拿出空闲线程来运行,并返回true。
示例:
class MyTask : public QRunnable {
public:void run() override {qDebug() << "Task is running in thread" << QThread::currentThread();// 模拟任务处理QThread::sleep(2);qDebug() << "Task completed in thread" << QThread::currentThread();}
};int main(int argc, char *argv[]) {QCoreApplication app(argc, argv);QThreadPool *threadPool = QThreadPool::globalInstance();threadPool->setMaxThreadCount(2); // 设置最大线程数for (int i = 0; i < 5; ++i) {MyTask *task = new MyTask();if (!threadPool->tryStart(task)) {qDebug() << "Failed to start task" << i << "due to lack of available threads";delete task; // 必须手动删除未能启动的任务以防止内存泄漏}}threadPool->waitForDone(); // 等待所有任务完成return app.exec();
}
bool tryTake(QRunnable *runnable)
尝试从线程池的等待队列中移除一个已经提交但尚未开始执行的runnable任务。
返回true表示移除成功,runnable对象的所有权将转移给调用者(即使runnable->autoDelete() == true)。
注意:
如果runnable->autoDelete() == true,调用tryTake()可以会删除错误的runnable。
比如runnable指针指向的任务执行完成后,被自动销毁了,但后面又在同一块内存分配了新的runnable,然后调用tryTake()时删除的是新的任务对象。
所以官方建议:只对runnable->autoDelete() == false的任务对象调用此函数。
bool waitForDone(int msecs = -1)
最多等待msecs毫秒,让所有线程退出并从线程池中删除所有线程。如果所有线程都被删除,则返回true。
如果msecs为默认值-1,则忽略超时,直到最后一个线程退出。
底层原理解析
线程状态
QSet<QThreadPoolThread *> allThreads;
QQueue<QThreadPoolThread *> waitingThreads;
QQueue<QThreadPoolThread *> expiredThreads;
QVector<QueuePage*> queue;
上面代码是QThreadPoolPrivate类的部分成员。
QSet<QThreadPoolThread *> allThreads
allThreads存储线程池中所有的线程对象,管理所有线程的生命周期、状态转换和清理工作。
QQueue<QThreadPoolThread *> waitingThreads
waitingThreads存储当前处于等待状态的线程对象,用于管理空闲线程,当有新任务到达时,可以从waitingThreads队列中取出一个线程来执行任务,从而避免频繁创建和销毁线程。
QQueue<QThreadPoolThread *> expiredThreads
expiredThreads存储当前处于过期状态的线程对象,用于管理过期线程,过期线程可能会被销毁以释放资源。
QVector<QueuePage*> queue
queue存储任务队列,按优先级管理任务,新任务会按优先级插入到适当的位置,以便高优先级的任务能够优先被执行。
小结:
所以,QThreadPool中的线程状态有:活动状态、空闲状态、过期状态。
- 活动状态:
活动状态指的是当前正在执行任务的线程状态,当前状态的线程对象存储在allThreads队列中。
- 空闲状态:
任务执行结束后线程进入空闲状态,在expiryTimeout过期时间内,线程对象将一直存储在waitingThreads队列中。直到超过过期时间后,线程将转为过期状态。
- 过期状态:
过期状态的线程会被批量释放。
线程池工作流程
添加任务流程:
线程池工作流程:
使用方法
子类化QRunnable
使用QThreadPool需要将子类化QRunnable作为线程任务对象。
class SimpleTask : public QRunnable {
protected:void run() override {qDebug() << "Simple task is running in thread" << QThread::currentThreadId();QThread::sleep(2); // 模拟线程任务耗时}
};int main(int argc, char *argv[]) {QCoreApplication a(argc, argv);SimpleTask *task = new SimpleTask();QThreadPool::globalInstance()->start(task);QThreadPool::globalInstance()->waitForDone(); // 等待所有线程结束return a.exec();
}
无参函数作为线程函数
void performTask()
{qDebug() << "Performing task in thread" << QThread::currentThreadId();
}int main(int argc, char* argv[])
{QApplication a(argc, argv);QThreadPool::globalInstance()->start(performTask);return a.exec();
}
带参函数作为线程函数
void performTask(int id)
{qDebug() << "Performing task in thread" << id << QThread::currentThreadId();
}int main(int argc, char* argv[])
{QApplication a(argc, argv);QThreadPool::globalInstance()->start(std::bind(performTask, 2));return a.exec();
}
Lambda表达式作为线程函数
int main(int argc, char* argv[])
{QApplication a(argc, argv);QThreadPool::globalInstance()->start([]() {qDebug() << "Lambda task running in thread" << QThread::currentThreadId();});return a.exec();
}
相关文章:
深入理解Qt多线程编程(QThreadPool)
多线程编程在现代软件开发中变得越来越重要,它能够提高应用程序的响应速度和处理性能。在Qt框架中,QThreadPool作为线程池管理工具,被频繁的使用。 目录 概述 接口介绍 底层原理解析 使用方法 概述 QThreadPool是Qt提供的一个线程池实现&a…...
Prisma数据库ORM框架学习
初始化项目 中文网站 点击快速开始,点击创建sql项目,后面一步一步往后走 这个博主也挺全的,推荐下 可以看这个页面初始化项目跟我下面是一样的,这里用得是ts,我下面是js,不需要额外的配置了 1.vscode打开一个空文件夹 2.npm init -y 初始化package.json 3.安装相关依赖 …...
Flutter-使用MethodChannel 实现与iOS交互
前言 使用 MethodChannel 在 Flutter 与原生 Android 和 iOS 之间进行通信,可以让你在 Flutter 应用中调用设备的原生功能。 基础概念 MethodChannel:Flutter 提供的通信机制,允许消息以方法调用的形式在 Flutter 与原生代码之间传递。方法…...
【星海随笔】云解决方案学习日志篇(一) ELK,kibana,Logstash安装
心路历程 本来想最近再研究研究DPDK的。但是自己做一个东西很多时候没有回报。因为自己的低学历问题,类似工作的面试都没有。所以很多东西学了很快就忘了,没有地方可以用。 今天看到了一个大佬,除了发型外,很多想法还是很共鸣的。 Shay Banon 决定开始跟…...
【leetcode】hot100 哈希表
1. 两数之和 1.1 题目 给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。 你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。…...
每日5题Day22 - LeetCode 106 - 110
每一步向前都是向自己的梦想更近一步,坚持不懈,勇往直前! 第一题:106. 从中序与后序遍历序列构造二叉树 - 力扣(LeetCode) class Solution {public TreeNode buildTree(int[] inorder, int[] postorder) {…...
【Python】读取文件夹中所有excel文件拼接成一个excel表格 的方法
我们平常会遇到下载了一些Excel文件放在一个文件夹下,而这些Excel文件的格式都一样,这时候需要批量这些文件合并成一个excel 文件里。 在Python中,我们可以使用pandas库来读取文件夹中的所有Excel文件,并将它们拼接成一个Excel表…...
7. 通配符和正则表达式
文章目录 7.1 通配符7.1.1 通配符介绍7.1.2 通配符示例 7.2 正则表达式7.2.1 grep命令7.2.2 基本正则表达式7.2.3 扩展正则表达式 7.1 通配符 在 Shell 中通配符用于查找文件名和目录名。它是由 Shell 处理的,只会出现在命令的参数中。 7.1.1 通配符介绍 * 匹…...
ROS2底层机制源码分析
init ->init_and_remove_ros_arguments ->init ->Context::init 保存初始化传入的信号 ->install_signal_handlers→SignalHandler::install 开线程响应信号 ->_remove_ros_arguments 移除ros参数 ->SingleNodeManager::instance().…...
超越 Transformer开启高效开放语言模型的新篇章
在人工智能快速发展的今天,对于高效且性能卓越的语言模型的追求,促使谷歌DeepMind团队开发出了RecurrentGemma这一突破性模型。这款新型模型在论文《RecurrentGemma:超越Transformers的高效开放语言模型》中得到了详细介绍,它通过…...
快速排序-Hoare 递归版 C语言
个人主页点这里~ 快速排序的简介: 快速排序是Hoare于1962年提出的一种 二叉树结构 的 交换 排序方法,其基本思想为:任取待排序元素序列中 的某元素作为 基准值 ,按照该排序码将待排序集合分割成 两子序列 , 左子序列中所有元素均 …...
C语言经典指针运算笔试题图文解析
指针运算常常出现在面试题中,画图解决是最好的办法。 题目1: #include <stdio.h> int main() {int a[5] { 1, 2, 3, 4, 5 };int* ptr (int*)(&a 1);printf("%d,%d", *(a 1), *(ptr - 1));return 0; } //程序的结果是什么&…...
使用 KubeKey v3.1.1 离线部署原生 Kubernetes v1.28.8 实战
今天,我将为大家实战演示,如何基于操作系统 openEuler 22.03 LTS SP3,利用 KubeKey 制作 Kubernetes 离线安装包,并实战离线部署 Kubernetes v1.28.8 集群。 实战服务器配置 (架构 1:1 复刻小规模生产环境,配置略有不…...
DOS 命令
Dos: Disk Operating System 磁盘操作系统, 简单说一下 windows 的目录结构。 ..\ 到上一级目录 常用的dos 命令: 查看当前目录是有什么内容 dir dir d:\abc2\test200切换到其他盘下:盘符号 cd : change directory 案例演示:切换…...
如何用Java程序实现一个简单的消息队列?
在Java程序中,可以使用内置的java.util.concurrent.BlockingQueue作为消息队列存放的容器,来实现一个简单的消息队列。 具体实现如下,在这个例子中,我们创建了一个生产者线程和一个消费者线程,他们共享同一个阻塞队列…...
OpenAI 宕机事件:GPT 停摆的影响与应对
引言 2024年6月4日,OpenAI 的 GPT 模型发生了一次全球性的宕机,持续时间长达8小时。此次宕机不仅影响了OpenAI自家的服务,还导致大量用户涌向竞争对手平台,如Claude和Gemini,结果也导致这些平台出现故障。这次事件的广…...
linux常用的基础命令
ls - 列出目录内容。 cd - 更改目录。 pwd - 打印当前工作目录。 mkdir - 创建新目录。 rmdir - 删除空目录。 touch - 创建新文件或更新现有文件的时间戳。 cp - 复制文件或目录。 mv - 移动或重命名文件或目录。 rm - 删除文件或目录。 cat - 显示文件内容。 more - 分页显示…...
618家用智能投影仪推荐:这个高性价比品牌不容错过
随着科技的不断进步,家庭影院的概念已经从传统的大屏幕电视逐渐转向了更为灵活和便携的家用智能投影仪。随着618电商大促的到来,想要购买投影仪的用户们也开始摩拳擦掌了。本文将从投影仪的基础知识入手,为您推荐几款性价比很高的投影仪&…...
自愿离婚协议书
自愿离婚协议书 男方(夫): 女方(妻): 双方现因 原因,导致夫妻情感已破裂,自愿离婚…...
WPS JSA 宏脚本入门和样例
1入门 WPS window版本才支持JSA宏的功能。 可以自动化的操作文档中的一些内容。 参考文档: WPS API 参考文档:https://open.wps.cn/previous/docs/client/wpsLoad 微软的Word API文档:Microsoft.Office.Interop.Word 命名空间 | Microsoft …...
浏览器访问 AWS ECS 上部署的 Docker 容器(监听 80 端口)
✅ 一、ECS 服务配置 Dockerfile 确保监听 80 端口 EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]或 EXPOSE 80 CMD ["python3", "-m", "http.server", "80"]任务定义(Task Definition&…...
Java 语言特性(面试系列2)
一、SQL 基础 1. 复杂查询 (1)连接查询(JOIN) 内连接(INNER JOIN):返回两表匹配的记录。 SELECT e.name, d.dept_name FROM employees e INNER JOIN departments d ON e.dept_id d.dept_id; 左…...
Spring Boot 实现流式响应(兼容 2.7.x)
在实际开发中,我们可能会遇到一些流式数据处理的场景,比如接收来自上游接口的 Server-Sent Events(SSE) 或 流式 JSON 内容,并将其原样中转给前端页面或客户端。这种情况下,传统的 RestTemplate 缓存机制会…...
在HarmonyOS ArkTS ArkUI-X 5.0及以上版本中,手势开发全攻略:
在 HarmonyOS 应用开发中,手势交互是连接用户与设备的核心纽带。ArkTS 框架提供了丰富的手势处理能力,既支持点击、长按、拖拽等基础单一手势的精细控制,也能通过多种绑定策略解决父子组件的手势竞争问题。本文将结合官方开发文档,…...
渲染学进阶内容——模型
最近在写模组的时候发现渲染器里面离不开模型的定义,在渲染的第二篇文章中简单的讲解了一下关于模型部分的内容,其实不管是方块还是方块实体,都离不开模型的内容 🧱 一、CubeListBuilder 功能解析 CubeListBuilder 是 Minecraft Java 版模型系统的核心构建器,用于动态创…...
【单片机期末】单片机系统设计
主要内容:系统状态机,系统时基,系统需求分析,系统构建,系统状态流图 一、题目要求 二、绘制系统状态流图 题目:根据上述描述绘制系统状态流图,注明状态转移条件及方向。 三、利用定时器产生时…...
c#开发AI模型对话
AI模型 前面已经介绍了一般AI模型本地部署,直接调用现成的模型数据。这里主要讲述讲接口集成到我们自己的程序中使用方式。 微软提供了ML.NET来开发和使用AI模型,但是目前国内可能使用不多,至少实践例子很少看见。开发训练模型就不介绍了&am…...
Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...
技术栈RabbitMq的介绍和使用
目录 1. 什么是消息队列?2. 消息队列的优点3. RabbitMQ 消息队列概述4. RabbitMQ 安装5. Exchange 四种类型5.1 direct 精准匹配5.2 fanout 广播5.3 topic 正则匹配 6. RabbitMQ 队列模式6.1 简单队列模式6.2 工作队列模式6.3 发布/订阅模式6.4 路由模式6.5 主题模式…...

