深入理解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 …...

多云管理“拦路虎”:深入解析网络互联、身份同步与成本可视化的技术复杂度
一、引言:多云环境的技术复杂性本质 企业采用多云策略已从技术选型升维至生存刚需。当业务系统分散部署在多个云平台时,基础设施的技术债呈现指数级积累。网络连接、身份认证、成本管理这三大核心挑战相互嵌套:跨云网络构建数据…...
基于大模型的 UI 自动化系统
基于大模型的 UI 自动化系统 下面是一个完整的 Python 系统,利用大模型实现智能 UI 自动化,结合计算机视觉和自然语言处理技术,实现"看屏操作"的能力。 系统架构设计 #mermaid-svg-2gn2GRvh5WCP2ktF {font-family:"trebuchet ms",verdana,arial,sans-…...
设计模式和设计原则回顾
设计模式和设计原则回顾 23种设计模式是设计原则的完美体现,设计原则设计原则是设计模式的理论基石, 设计模式 在经典的设计模式分类中(如《设计模式:可复用面向对象软件的基础》一书中),总共有23种设计模式,分为三大类: 一、创建型模式(5种) 1. 单例模式(Sing…...
云计算——弹性云计算器(ECS)
弹性云服务器:ECS 概述 云计算重构了ICT系统,云计算平台厂商推出使得厂家能够主要关注应用管理而非平台管理的云平台,包含如下主要概念。 ECS(Elastic Cloud Server):即弹性云服务器,是云计算…...

Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)
文章目录 1.什么是Redis?2.为什么要使用redis作为mysql的缓存?3.什么是缓存雪崩、缓存穿透、缓存击穿?3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...

MMaDA: Multimodal Large Diffusion Language Models
CODE : https://github.com/Gen-Verse/MMaDA Abstract 我们介绍了一种新型的多模态扩散基础模型MMaDA,它被设计用于在文本推理、多模态理解和文本到图像生成等不同领域实现卓越的性能。该方法的特点是三个关键创新:(i) MMaDA采用统一的扩散架构…...
五年级数学知识边界总结思考-下册
目录 一、背景二、过程1.观察物体小学五年级下册“观察物体”知识点详解:由来、作用与意义**一、知识点核心内容****二、知识点的由来:从生活实践到数学抽象****三、知识的作用:解决实际问题的工具****四、学习的意义:培养核心素养…...

Mac软件卸载指南,简单易懂!
刚和Adobe分手,它却总在Library里给你写"回忆录"?卸载的Final Cut Pro像电子幽灵般阴魂不散?总是会有残留文件,别慌!这份Mac软件卸载指南,将用最硬核的方式教你"数字分手术"࿰…...
Linux离线(zip方式)安装docker
目录 基础信息操作系统信息docker信息 安装实例安装步骤示例 遇到的问题问题1:修改默认工作路径启动失败问题2 找不到对应组 基础信息 操作系统信息 OS版本:CentOS 7 64位 内核版本:3.10.0 相关命令: uname -rcat /etc/os-rele…...
Java求职者面试指南:Spring、Spring Boot、MyBatis框架与计算机基础问题解析
Java求职者面试指南:Spring、Spring Boot、MyBatis框架与计算机基础问题解析 一、第一轮提问(基础概念问题) 1. 请解释Spring框架的核心容器是什么?它在Spring中起到什么作用? Spring框架的核心容器是IoC容器&#…...