当前位置: 首页 > news >正文

深入理解Qt多线程编程(QThreadPool)

多线程编程在现代软件开发中变得越来越重要,它能够提高应用程序的响应速度和处理性能。在Qt框架中,QThreadPool作为线程池管理工具,被频繁的使用。

目录

概述
接口介绍
底层原理解析
使用方法

概述

QThreadPool是Qt提供的一个线程池实现,用于管理和复用线程。线程池通过复用现有的线程来避免频繁创建和销毁线程带来的性能开销,适用于需要频繁执行并发任务的场景。QThreadPool内部维护了一个线程队列,可以在程序运行时动态分配和管理线程资源。

接口介绍

int activeThreadCount() const

返回当前活动(正在运行任务)线程的数量。这个数字不包括闲置等待工作的线程。

void clear()

清除线程池的任务队列。已经开始的任务将会继续完成,但队列中等待开始的任务会被取消。

bool contains(const QThread *thread) const

检查指定的线程是否属于该线程池。

int expiryTimeout() constvoid setExpiryTimeout(int expiryTimeout)

返回/设置线程过期时间,单位是毫秒。
线程在完成任务后,如果在过期时间内没有心的任务分配给它,那么它将被销毁。
默认expiryTimeout值为30000毫秒(30秒)。

注意:
设置expiryTimeout对已运行的线程没有影响。只有新创建的线程才会使用新的expiryTimeout。我们建议在创建线程池之后,调用start()之前设置expiryTimeout

int maxThreadCount() constvoid 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() constvoid 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)

尝试从线程池中拿出空闲的线程来运行runnablefunctionToRun
如果调用时,没有空闲的线程,则此函数直接返回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队列中。直到超过过期时间后,线程将转为过期状态。

  • 过期状态

过期状态的线程会被批量释放。

线程池工作流程

添加任务流程:

未命名文件 (9).png

线程池工作流程:

未命名文件 (10).png

使用方法

子类化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)

多线程编程在现代软件开发中变得越来越重要&#xff0c;它能够提高应用程序的响应速度和处理性能。在Qt框架中&#xff0c;QThreadPool作为线程池管理工具&#xff0c;被频繁的使用。 目录 概述 接口介绍 底层原理解析 使用方法 概述 QThreadPool是Qt提供的一个线程池实现&a…...

Prisma数据库ORM框架学习

初始化项目 中文网站 点击快速开始,点击创建sql项目,后面一步一步往后走 这个博主也挺全的,推荐下 可以看这个页面初始化项目跟我下面是一样的,这里用得是ts,我下面是js,不需要额外的配置了 1.vscode打开一个空文件夹 2.npm init -y 初始化package.json 3.安装相关依赖 …...

Flutter-使用MethodChannel 实现与iOS交互

前言 使用 MethodChannel 在 Flutter 与原生 Android 和 iOS 之间进行通信&#xff0c;可以让你在 Flutter 应用中调用设备的原生功能。 基础概念 MethodChannel&#xff1a;Flutter 提供的通信机制&#xff0c;允许消息以方法调用的形式在 Flutter 与原生代码之间传递。方法…...

【星海随笔】云解决方案学习日志篇(一) ELK,kibana,Logstash安装

心路历程 本来想最近再研究研究DPDK的。但是自己做一个东西很多时候没有回报。因为自己的低学历问题&#xff0c;类似工作的面试都没有。所以很多东西学了很快就忘了&#xff0c;没有地方可以用。 今天看到了一个大佬,除了发型外,很多想法还是很共鸣的。 Shay Banon 决定开始跟…...

【leetcode】hot100 哈希表

1. 两数之和 1.1 题目 给定一个整数数组 nums 和一个整数目标值 target&#xff0c;请你在该数组中找出 和为目标值 target 的那 两个 整数&#xff0c;并返回它们的数组下标。 你可以假设每种输入只会对应一个答案。但是&#xff0c;数组中同一个元素在答案里不能重复出现。…...

每日5题Day22 - LeetCode 106 - 110

每一步向前都是向自己的梦想更近一步&#xff0c;坚持不懈&#xff0c;勇往直前&#xff01; 第一题&#xff1a;106. 从中序与后序遍历序列构造二叉树 - 力扣&#xff08;LeetCode&#xff09; class Solution {public TreeNode buildTree(int[] inorder, int[] postorder) {…...

【Python】读取文件夹中所有excel文件拼接成一个excel表格 的方法

我们平常会遇到下载了一些Excel文件放在一个文件夹下&#xff0c;而这些Excel文件的格式都一样&#xff0c;这时候需要批量这些文件合并成一个excel 文件里。 在Python中&#xff0c;我们可以使用pandas库来读取文件夹中的所有Excel文件&#xff0c;并将它们拼接成一个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 处理的&#xff0c;只会出现在命令的参数中。 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开启高效开放语言模型的新篇章

在人工智能快速发展的今天&#xff0c;对于高效且性能卓越的语言模型的追求&#xff0c;促使谷歌DeepMind团队开发出了RecurrentGemma这一突破性模型。这款新型模型在论文《RecurrentGemma&#xff1a;超越Transformers的高效开放语言模型》中得到了详细介绍&#xff0c;它通过…...

快速排序-Hoare 递归版 C语言

个人主页点这里~ 快速排序的简介: 快速排序是Hoare于1962年提出的一种 二叉树结构 的 交换 排序方法&#xff0c;其基本思想为&#xff1a;任取待排序元素序列中 的某元素作为 基准值 &#xff0c;按照该排序码将待排序集合分割成 两子序列 &#xff0c; 左子序列中所有元素均 …...

C语言经典指针运算笔试题图文解析

指针运算常常出现在面试题中&#xff0c;画图解决是最好的办法。 题目1&#xff1a; #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 实战

今天&#xff0c;我将为大家实战演示&#xff0c;如何基于操作系统 openEuler 22.03 LTS SP3&#xff0c;利用 KubeKey 制作 Kubernetes 离线安装包&#xff0c;并实战离线部署 Kubernetes v1.28.8 集群。 实战服务器配置 (架构 1:1 复刻小规模生产环境&#xff0c;配置略有不…...

DOS 命令

Dos&#xff1a; Disk Operating System 磁盘操作系统, 简单说一下 windows 的目录结构。 ..\ 到上一级目录 常用的dos 命令&#xff1a; 查看当前目录是有什么内容 dir dir d:\abc2\test200切换到其他盘下&#xff1a;盘符号 cd : change directory 案例演示&#xff1a;切换…...

如何用Java程序实现一个简单的消息队列?

在Java程序中&#xff0c;可以使用内置的java.util.concurrent.BlockingQueue作为消息队列存放的容器&#xff0c;来实现一个简单的消息队列。 具体实现如下&#xff0c;在这个例子中&#xff0c;我们创建了一个生产者线程和一个消费者线程&#xff0c;他们共享同一个阻塞队列…...

OpenAI 宕机事件:GPT 停摆的影响与应对

引言 2024年6月4日&#xff0c;OpenAI 的 GPT 模型发生了一次全球性的宕机&#xff0c;持续时间长达8小时。此次宕机不仅影响了OpenAI自家的服务&#xff0c;还导致大量用户涌向竞争对手平台&#xff0c;如Claude和Gemini&#xff0c;结果也导致这些平台出现故障。这次事件的广…...

linux常用的基础命令

ls - 列出目录内容。 cd - 更改目录。 pwd - 打印当前工作目录。 mkdir - 创建新目录。 rmdir - 删除空目录。 touch - 创建新文件或更新现有文件的时间戳。 cp - 复制文件或目录。 mv - 移动或重命名文件或目录。 rm - 删除文件或目录。 cat - 显示文件内容。 more - 分页显示…...

618家用智能投影仪推荐:这个高性价比品牌不容错过

随着科技的不断进步&#xff0c;家庭影院的概念已经从传统的大屏幕电视逐渐转向了更为灵活和便携的家用智能投影仪。随着618电商大促的到来&#xff0c;想要购买投影仪的用户们也开始摩拳擦掌了。本文将从投影仪的基础知识入手&#xff0c;为您推荐几款性价比很高的投影仪&…...

自愿离婚协议书

自愿离婚协议书 男方&#xff08;夫&#xff09;&#xff1a; 女方&#xff08;妻&#xff09;&#xff1a; 双方现因 原因&#xff0c;导致夫妻情感已破裂&#xff0c;自愿离婚…...

WPS JSA 宏脚本入门和样例

1入门 WPS window版本才支持JSA宏的功能。 可以自动化的操作文档中的一些内容。 参考文档&#xff1a; WPS API 参考文档&#xff1a;https://open.wps.cn/previous/docs/client/wpsLoad 微软的Word API文档&#xff1a;Microsoft.Office.Interop.Word 命名空间 | Microsoft …...

Linux链表操作全解析

Linux C语言链表深度解析与实战技巧 一、链表基础概念与内核链表优势1.1 为什么使用链表&#xff1f;1.2 Linux 内核链表与用户态链表的区别 二、内核链表结构与宏解析常用宏/函数 三、内核链表的优点四、用户态链表示例五、双向循环链表在内核中的实现优势5.1 插入效率5.2 安全…...

Vue2 第一节_Vue2上手_插值表达式{{}}_访问数据和修改数据_Vue开发者工具

文章目录 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染2. 插值表达式{{}}3. 访问数据和修改数据4. vue响应式5. Vue开发者工具--方便调试 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染 准备容器引包创建Vue实例 new Vue()指定配置项 ->渲染数据 准备一个容器,例如: …...

【RockeMQ】第2节|RocketMQ快速实战以及核⼼概念详解(二)

升级Dledger高可用集群 一、主从架构的不足与Dledger的定位 主从架构缺陷 数据备份依赖Slave节点&#xff0c;但无自动故障转移能力&#xff0c;Master宕机后需人工切换&#xff0c;期间消息可能无法读取。Slave仅存储数据&#xff0c;无法主动升级为Master响应请求&#xff…...

Unit 1 深度强化学习简介

Deep RL Course ——Unit 1 Introduction 从理论和实践层面深入学习深度强化学习。学会使用知名的深度强化学习库&#xff0c;例如 Stable Baselines3、RL Baselines3 Zoo、Sample Factory 和 CleanRL。在独特的环境中训练智能体&#xff0c;比如 SnowballFight、Huggy the Do…...

自然语言处理——循环神经网络

自然语言处理——循环神经网络 循环神经网络应用到基于机器学习的自然语言处理任务序列到类别同步的序列到序列模式异步的序列到序列模式 参数学习和长程依赖问题基于门控的循环神经网络门控循环单元&#xff08;GRU&#xff09;长短期记忆神经网络&#xff08;LSTM&#xff09…...

精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南

精益数据分析&#xff08;97/126&#xff09;&#xff1a;邮件营销与用户参与度的关键指标优化指南 在数字化营销时代&#xff0c;邮件列表效度、用户参与度和网站性能等指标往往决定着创业公司的增长成败。今天&#xff0c;我们将深入解析邮件打开率、网站可用性、页面参与时…...

【数据分析】R版IntelliGenes用于生物标志物发现的可解释机器学习

禁止商业或二改转载&#xff0c;仅供自学使用&#xff0c;侵权必究&#xff0c;如需截取部分内容请后台联系作者! 文章目录 介绍流程步骤1. 输入数据2. 特征选择3. 模型训练4. I-Genes 评分计算5. 输出结果 IntelliGenesR 安装包1. 特征选择2. 模型训练和评估3. I-Genes 评分计…...

LINUX 69 FTP 客服管理系统 man 5 /etc/vsftpd/vsftpd.conf

FTP 客服管理系统 实现kefu123登录&#xff0c;不允许匿名访问&#xff0c;kefu只能访问/data/kefu目录&#xff0c;不能查看其他目录 创建账号密码 useradd kefu echo 123|passwd -stdin kefu [rootcode caozx26420]# echo 123|passwd --stdin kefu 更改用户 kefu 的密码…...

音视频——I2S 协议详解

I2S 协议详解 I2S (Inter-IC Sound) 协议是一种串行总线协议&#xff0c;专门用于在数字音频设备之间传输数字音频数据。它由飞利浦&#xff08;Philips&#xff09;公司开发&#xff0c;以其简单、高效和广泛的兼容性而闻名。 1. 信号线 I2S 协议通常使用三根或四根信号线&a…...

Selenium常用函数介绍

目录 一&#xff0c;元素定位 1.1 cssSeector 1.2 xpath 二&#xff0c;操作测试对象 三&#xff0c;窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四&#xff0c;弹窗 五&#xff0c;等待 六&#xff0c;导航 七&#xff0c;文件上传 …...