深入理解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 …...
第25节 Node.js 断言测试
Node.js的assert模块主要用于编写程序的单元测试时使用,通过断言可以提早发现和排查出错误。 稳定性: 5 - 锁定 这个模块可用于应用的单元测试,通过 require(assert) 可以使用这个模块。 assert.fail(actual, expected, message, operator) 使用参数…...
Caliper 配置文件解析:config.yaml
Caliper 是一个区块链性能基准测试工具,用于评估不同区块链平台的性能。下面我将详细解释你提供的 fisco-bcos.json 文件结构,并说明它与 config.yaml 文件的关系。 fisco-bcos.json 文件解析 这个文件是针对 FISCO-BCOS 区块链网络的 Caliper 配置文件,主要包含以下几个部…...
html-<abbr> 缩写或首字母缩略词
定义与作用 <abbr> 标签用于表示缩写或首字母缩略词,它可以帮助用户更好地理解缩写的含义,尤其是对于那些不熟悉该缩写的用户。 title 属性的内容提供了缩写的详细说明。当用户将鼠标悬停在缩写上时,会显示一个提示框。 示例&#x…...
Scrapy-Redis分布式爬虫架构的可扩展性与容错性增强:基于微服务与容器化的解决方案
在大数据时代,海量数据的采集与处理成为企业和研究机构获取信息的关键环节。Scrapy-Redis作为一种经典的分布式爬虫架构,在处理大规模数据抓取任务时展现出强大的能力。然而,随着业务规模的不断扩大和数据抓取需求的日益复杂,传统…...
CVPR2025重磅突破:AnomalyAny框架实现单样本生成逼真异常数据,破解视觉检测瓶颈!
本文介绍了一种名为AnomalyAny的创新框架,该方法利用Stable Diffusion的强大生成能力,仅需单个正常样本和文本描述,即可生成逼真且多样化的异常样本,有效解决了视觉异常检测中异常样本稀缺的难题,为工业质检、医疗影像…...
面试高频问题
文章目录 🚀 消息队列核心技术揭秘:从入门到秒杀面试官1️⃣ Kafka为何能"吞云吐雾"?性能背后的秘密1.1 顺序写入与零拷贝:性能的双引擎1.2 分区并行:数据的"八车道高速公路"1.3 页缓存与批量处理…...
大模型——基于Docker+DeepSeek+Dify :搭建企业级本地私有化知识库超详细教程
基于Docker+DeepSeek+Dify :搭建企业级本地私有化知识库超详细教程 下载安装Docker Docker官网:https://www.docker.com/ 自定义Docker安装路径 Docker默认安装在C盘,大小大概2.9G,做这行最忌讳的就是安装软件全装C盘,所以我调整了下安装路径。 新建安装目录:E:\MyS…...
华为云Flexus+DeepSeek征文 | MaaS平台避坑指南:DeepSeek商用服务开通与成本控制
作者简介 我是摘星,一名专注于云计算和AI技术的开发者。本次通过华为云MaaS平台体验DeepSeek系列模型,将实际使用经验分享给大家,希望能帮助开发者快速掌握华为云AI服务的核心能力。 目录 作者简介 前言 一、技术架构概览 1.1 整体架构设…...
第21节 Node.js 多进程
Node.js本身是以单线程的模式运行的,但它使用的是事件驱动来处理并发,这样有助于我们在多核 cpu 的系统上创建多个子进程,从而提高性能。 每个子进程总是带有三个流对象:child.stdin, child.stdout和child.stderr。他们可能会共享…...
电脑定时关机工具推荐
软件介绍 本文介绍一款轻量级的电脑自动关机工具,无需安装,使用简单,可满足定时关机需求。 工具简介 这款关机助手是一款无需安装的小型软件,文件体积仅60KB,下载后可直接运行,无需复杂配置。 使用…...

