深入理解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();
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

计算机视觉全系列实战教程:(八)图像变换-点运算、灰度变换、直方图变换

图像变换&#xff1a;点运算、灰度变换、直方图变换 1.点运算(1)What(2)Why 2.灰度变换(1)What(2)Why(作用)(3)Which(有哪些灰度变换&#xff09; 3.直方图修正(1)直方图均衡化 1.点运算 (1)What 通过点运算&#xff0c;输出图像的每个像素的灰度值仅仅取决于输入图像中相对应…

eNSP学习——PPP的认证

目录 主要命令 原理概述 实验目的 实验内容 实验拓扑 实验编址 实验步骤 1、基本配置 2、搭建OSPF网络 3、配置PPP的PAP认证 4、配置PPP的CHAP认证 主要命令 //设置本端的PPP协议对对端设备的认证方式为 PAP&#xff0c;认证采用的域名为huawei [R3]int s4/0/0 [R…

C语言 RTC时间(年月日时分秒) 和 时间戳 互相转换

一、介绍 在C语言中&#xff0c;将年月日时分秒转换为时间戳&#xff08;Unix时间戳&#xff0c;即从1970年1月1日00:00:00 UTC到现在的秒数&#xff09;通常需要使用struct tm结构体和timegm或mktime函数。&#xff08;注意&#xff0c;mktime函数假设struct tm是本地时间&…

面试题分享之JVM篇

注意&#xff1a;文章若有错误的地方&#xff0c;欢迎评论区里面指正 &#x1f36d; 系列文章目录 面试题分享之Java并发篇面试题分享之Java集合篇&#xff08;二&#xff09; 前言 学过Java的小伙伴肯定对JVM&#xff08;Java虚拟机&#xff09;多多少少了解一点&#xff0c…

工程项目管理系统:高效、专业的工程管理软件

在当今快速发展的工程行业&#xff0c;有效的项目管理是确保项目成功的关键。鸿鹄工程项目管理系统&#xff0c;基于Spring Cloud、Spring Boot、Mybatis、Vue和ElementUI技术栈&#xff0c;提供了一个全面、高效的解决方案&#xff0c;以应对复杂的工程项目管理挑战。 项目背景…

ChatGLM3-6B-32K 在linux(Ubuntu) GPU P100(16G)复现记录

ChatGLM3-6B-32K 在linux(Ubuntu) GPU P100(16G)复现记录 时间&#xff1a;2024年6月12日 1.创建Conda环境 conda create --name chatglm3 python3.10 conda activate chatglm32.下载chatglm&#xff0c;并安装依赖 如果没有安装git命令&#xff0c;先安装git命令 sudo ap…

开展文化科技卫生“三下乡”活动怎样向媒体投稿宣传?

在新时代背景下&#xff0c;“文化科技卫生三下乡”活动作为推动乡村振兴、促进城乡融合发展的重要举措&#xff0c;正发挥着不可小觑的作用。这类活动通过输送文化、科技、卫生等领域的资源和服务到农村&#xff0c;极大地丰富了农民的精神生活&#xff0c;提升了农村的科学素…

【目标检测】基于深度学习的车牌识别管理系统(含UI界面)【python源码+Pyqt5界面 MX_002期】

系统简介&#xff1a; 车牌识别技术作为经典的机器视觉任务&#xff0c;具有广泛的应用前景。通过图像处理方法&#xff0c;车牌识别技术能够对车牌上的字符进行检测、定位和识别&#xff0c;从而实现计算机对车牌的智能化管理。在现实生活中&#xff0c;车牌识别系统已在小区停…

前端传递bool型后端用int收不到

文章目录 背景模拟错误点解决方法 背景 我前几天遇到一个低级错误&#xff0c;就是我前端发一个请求&#xff0c;把参数送到后端&#xff0c;但是我参数里面无意间传的布尔型&#xff08;刚开始一直没注意到&#xff0c;因为当时参数有十几个&#xff09;&#xff0c;但是我后…

认证体系下的CID广告服务商:构建商家与服务商的共赢生态

摘要&#xff1a;引流电商服务商认证体系的构建促进了商家与服务商之间的顺畅合作&#xff0c;降低了风险&#xff0c;优化了资源配置。认证提升了服务商形象&#xff0c;扩大了市场份额&#xff0c;推动了行业技术创新和服务升级&#xff0c;构建了共赢生态。 在引流电商行业…

发布会后苹果股价创历史新高;商汤 Embedding 模型拿下 SOTA丨 RTE 开发者日报 Vol.223

开发者朋友们大家好&#xff1a; 这里是 「RTE 开发者日报」 &#xff0c;每天和大家一起看新闻、聊八卦。我们的社区编辑团队会整理分享 RTE&#xff08;Real-Time Engagement&#xff09; 领域内「有话题的 新闻 」、「有态度的 观点 」、「有意思的 数据 」、「有思考的 文…

免密支付存隐患 谨防“便捷”变“踩坑”

免密支付存隐患 谨防“便捷”变“踩坑” 当前&#xff0c;我国网购用户已超9亿人&#xff0c;越来越便捷的支付手段让网络消费体验更加“丝滑”。但免密支付、自动续费等方式在简化付款流程的同时&#xff0c;也成为一些平台“套路”消费者的手段&#xff0c;暗藏诱导消费陷阱。…

闪烁与常亮的符号状态判断机制(状态机算法)

背景说明 在视觉项目中&#xff0c;经常要判断目标的状态&#xff0c;例如&#xff1a;符号的不同频率闪烁、常亮等。然而常规的视觉算法例如YOLO&#xff0c;仅仅只能获取当前帧是否存在该符号&#xff0c;而无法对于符号状态进行判断&#xff0c;然而重新写一个基于时序的卷积…

跟着AI学AI_09 PyTorch 简介

PyTorch 简介 PyTorch 是一个开源的深度学习框架&#xff0c;由 Facebook 的人工智能研究团队&#xff08;FAIR&#xff09;开发。它提供了灵活且高效的张量计算功能&#xff0c;并支持动态计算图。PyTorch 的易用性和灵活性使其成为深度学习研究和生产应用中广泛使用的工具。…

一维、二维数组练习题

1、输入一个6个元素的一维数组&#xff0c;实现冒泡排序 2、输入一个6个元素的一维数组&#xff0c;实现简单排序 3、输入一个5个元素的一维数组&#xff0c;求最大值&#xff0c;最小值 4、输入一个三行四列的二维数组&#xff0c;计算最大值和最小值

期权和股票有什么区别?

今天带你了解期权和股票有什么区别&#xff1f;股票和期权都是投资产品&#xff0c;但它们却是两种截然不同的交易模式&#xff0c;在开户要求上也有很多差别。 期权和股票有什么区别&#xff1f; 权利与义务&#xff1a; 股票&#xff1a;代表公司的所有权的一部分&#xff…

【扫码点餐系统】制作搭建部署

前言&#xff1a; 餐饮类做一个扫码点餐的工具可以提升用户体验、扩大市场份额、提高运营效率以及适应数字化趋势等方面。 一、企业开发小程序原因 企业开发小程序具有多方面的优势&#xff0c;可以帮助企业提升用户体验、扩大市场份额、提高运营效率以及适应数字化趋势等。…

线程安全问题【snychornized 、死锁、线程通信】

目录 一、线程安全1.1 线程安全问题?1.2 如何解决线程安全问题方法具体如何实现? 1.3 同步方法1.4 同步代码块1.5 总结1.6 售票例子1.8 补充 二、线程安全的集合三、死锁【了解】四、线程通信4.1 同步方法4.2 同步代码块4.3 wait和sleep本篇的思维导图 最后 一、线程安全 1.…

批量替换删除图片文件名称中相同数字:轻松管理文件结构新技巧大揭秘

特别是当图片文件名称中包含相同的数字时&#xff0c;想要快速找到或整理这些文件更是难上加难。今天&#xff0c;我要向大家揭秘一种轻松管理图片文件结构的新软件——文件批量改名高手。 进入“文件批量改命名高手”主页面&#xff0c;你会看到一个简洁明了的操作界面。在板…

【Linux】模拟实现一个简单的日志系统

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前正在学习c和算法 ✈️专栏&#xff1a;Linux &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章有啥瑕疵&#xff0c;希望大佬指点一二 如果文章对…