Qt线程池QThreadPool的使用详解

目录
  • 一、目的
  • 二、最优线程数
  • 三、线程池的原理
  • 四、QThreadPool线程池
  • 五、QThreadPool简单示例

一、目的

  现在所有的高性能服务器程序,几乎都会使用到线程池技术,从而更好且有效的榨干服务器性能。而创建并销毁线程的过程势必会消耗内存。而在日常开发中内存资源是及其宝贵的,所以QT 多线程之线程池QThreadPool就有很大用处了。它可以用来管理线程的优先顺序,防止创建过多的线程,起到很好的管理作用。

二、最优线程数

  线程的创建和销毁是有性能开销的,当我们有少量业务需要处理时,我们可以放到线程中完成,甚至可以多开几个线程并行处理。
那么,问题来了,如果需要海量的数据处理,难道无休止的开线程下去吗?

首先,要明白CPU的性能是有限的,每个线程好比一个处理时间片,多个线程之间切换处理,CPU线程上下文来回切换,这个也是需要消耗时间的。所以,物极必反,当线程数量到达一个点后,可能消耗在线程切换的时间,会大于实际线程处理业务的时间,这个可以想象的到。

那么很容易明白:线程数并不是越多越好,而是某个范围或者某个经验值。

一般来讲,我们可以认为,最佳性能线程数==CPU逻辑核心数量,比如CPU是4核8线程,那么开8个线程可以达到性能最佳。
一般电脑是开启超线程的,也就是4核可以模拟出8个逻辑核,故称4核8线程。
QThreadPool线程池默认最大线程数,也是CPU逻辑Core的数量。
严格意义来讲,最佳线程数还与处理业务类型有关,如业务属于IO密集型、CPU密集型,根据经验推断:

  • IO密集型,频繁读取磁盘上的数据,或者需要通过网络远程调用接口。线程数经验值是:2N,其中N代表CPU逻辑Core数;
  • CPU密集型,非常复杂的调用,循环次数很多,或者递归调用层次很深等。线程数经验值是:N + 1,其中N代表CPU逻辑Core数。

三、线程池的原理

最佳性能线程数可以认为等于CPU逻辑核心数量N,所以我们设计程序,为了得到更好的性能,需要实现如下的需求:

  • 限制创建最大线程数量<=N;
  • 尽可能复用线程,避免频繁创建和销毁线程资源,降低无谓消耗;
  • 线程在空闲时,应该休息,避免占用CPU资源;
  • 线程在有业务需要处理时,需要激活;
  • 当业务来了,这N个线程如何分配;

上述问题,高度封装的QThreadPool线程池可以解决。

线程池的优点:

  • 创建和销毁线程需要和OS交互,少量线程影响不大,但是线程数量太大,势必会影响性能,使用线程池可以这种开销;
  • 线程池维护一定数量的线程,使用时,将指定函数传递给线程池,线程池会在线程中执行任务;
  • 线程池,属于对象池,对象池都是为了复用,以避免频繁申请和释放对象所造成的性能损失。
  • 线程池创建好后,池内默认一个线程也没有,当通过相关函数加入任务后,线程池根据任务数量会自动创建线程,任务会合理分配到各个线程上执行,但是线程总数量不会超过设定的最大值。
  • 若任务处理完毕,则池内所有线程进入挂起状态,不占用CPU时间片,待任务再次到来,便会激活部分或全部线程,处理任务。
  • 若任务过多,当前没有空闲的线程,则新增任务会被放置到缓存队列中,等待线程空闲后,再进行处理,这样,每个任务与线程可以有一个合理的分配,相当于实现了业务处理的负载均衡。故而可以以最好的性能来处理业务。

四、QThreadPool线程池

下面是QThreadPool的常用函数:

int activeThreadCount() const //当前的活动线程数量

void clear()//清除所有当前排队但未开始运行的任务

int expiryTimeout() const//线程长时间未使用将会自动退出节约资源,此函数返回等待时间

int maxThreadCount() const//线程池可维护的最大线程数量

void releaseThread()//释放被保留的线程

void reserveThread()//保留线程,此线程将不会占用最大线程数量,从而可能会引起当前活动线程数量大于最大线程数量的情况

void setExpiryTimeout(int expiryTimeout)//设置线程回收的等待时间

void setMaxThreadCount(int maxThreadCount)//设置最大线程数量

void setStackSize(uint stackSize)//此属性包含线程池工作线程的堆栈大小。

uint stackSize() const//堆大小

void start(QRunnable *runnable, int priority = 0)//加入一个运算到队列,注意start不一定立刻启动,只是插入到队列,排到了才会开始运行。需要传入QRunnable ,后续介绍

bool tryStart(QRunnable *runnable)//尝试启动一个

bool tryTake(QRunnable *runnable)//删除队列中的一个QRunnable,若当前QRunnable 未启动则返回成功,正在运行则返回失败

bool waitForDone(int?<i>msecs</i>?=?-1)//等待所有线程运行结束并退出,参数为等待时间-1表示一直等待到最后一个线程退出

QRunnable类:所有runable对象的基类。
QRunnable类是一个接口, 用于表示需要执行的任务或代码段, 具体任务在run() 函数内部实现。可以使用QThreadPool在各个独立的线程中执行代码。如果autoDelete() 返回true (默认值), QThreadPool将自动删除QRunnable 。使用setAutoDelete() 可更改是否自动删除。
QThreadPool 是创建线程池函数,QRunnable是线程池的线程具体执行操作函数,两者要搭配使用。

五、QThreadPool简单示例

执行效果如下:

#include <QCoreApplication>
#include <QThreadPool>
#include <QDebug>

class Task1 : public QRunnable
{
public:
    Task1()
    { }
    virtual ~Task1() override
    {
        qDebug() << "~Task1()";
    }

    virtual void run() override
    {
        qDebug() << "do Task1 work:" << QThread::currentThreadId();
    }
};

class Task2 : public QRunnable
{
public:
    Task2()
    { }
    virtual ~Task2() override
    {
        qDebug() << "~Task2()";
    }

    virtual void run() override
    {
        qDebug() << "do Task2 work:" << QThread::currentThreadId();
    }
};

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    Task1* task1 = new Task1();
    Task2* task2 = new Task2();

    QThreadPool threadPool;
    threadPool.start(task1);
    threadPool.start(task2);
    threadPool.waitForDone();

    return a.exec();
}

注意:
线程池使用时传入继承于的QRunnable类对象(并启动该线程对象),并且线程池会自主释放在其中的线程(提高程序性能),还能实现并发,提高效率;不过不能使用信号槽进行通信,需要使用QMetaObject::invokeMethod进行通信。

到此这篇关于Qt线程池QThreadPool的使用详解的文章就介绍到这了,更多相关Qt线程池QThreadPool内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 利用PyQt中的QThread类实现多线程

    用PyQt中的QThread类实现多线程 利用PyQt中的pyqtSignal类实现信息的触发和捕获,即定义事件和订阅事件 利用QThread类实现多线程的方法 1,新建一个python类,继承自QThread from PyQt5.QtCore import QThread class SubThread(QThread): 2,重写__init__(),__del__()和run()函数 from PyQt5.QtCore import QThread class SubThread(QTh

  • Qt实现线程与定时器的方法

    目录 一.定时器QTimer类 二.在多线程中使用QTimer 1.错误用法 2.正确用法一 3.正确用法二 一.定时器QTimer类 The QTimer class provides repetitive and single-shot timers. The QTimer class provides a high-level programming interface for timers. To use it, create a QTimer, connect its timeout(

  • Qt基础开发之Qt多线程类QThread与Qt定时器类QTimer的详细方法与实例

    Qt多线程 我们之前的程序都是单线程运行,接下来我们开始引入多线程.就相当于以前的一个人在工作,现在多个人一起工作. Qt中非常有必要使用多线程,这是因为,Qt应用是事件驱动型的,一旦某个事件处理函数处理时间过久,就会造成其它的事件得不到及时处理. Qt中使用QThread来管理线程,一个QThread对象,就是一个线程.QThread对象也有消息循序exec()函数,用来处理自己这个线程的事件. Qt实现多线程有两种方式 ​1.Qt第一种创建线程方式 首先要继承QThread 重写虚函数QTh

  • Qt 中开启线程的多种方式小结

    目录 简介 1. 继承 QThread 重写 run 函数 2. 继承 QObject 调用 moveToThread 3. 继承 QRunnable 重新 run 函数,结合 QThreadPool 实现线程池 4. 使用 C++ 11 中的 sth::thread 5. Qt QtConcurrent 之 Run 函数 简介 在开发过程中,使用线程是经常会遇到的场景,本篇文章就来整理一下 Qt 中使用线程的五种方式,方便后期回顾.前面两种比较简单,一笔带过了,主要介绍后面三种.最后两种方法博

  • Qt5多线程编程的实现

    目录 一.线程基础 1.GUI线程与工作线程 2.数据的同步访问 二.QT多线程简介 三.QThread线程 四.简单实例 一.线程基础 1.GUI线程与工作线程 每个程序启动后拥有的第一个线程称为主线程,即GUI线程.QT中所有的组件类和几个相关的类只能工作在GUI线程,不能工作在次线程,次线程即工作线程,主要负责处理GUI线程卸下的工作. 2.数据的同步访问 每个线程都有自己的栈,因此每个线程都要自己的调用历史和本地变量.线程共享相同的地址空间. 二.QT多线程简介     Qt通过三种形式

  • QT实现多线程两种方式案例详解

    Qt线程 Qt4.7之前版本处理步骤 1.自定义一个类,继承于QThread. class MyThread:public QThread{ public: vid run(); //虚函数 线程处理函数(和主线程不在同一个线程) signals: void isDone(); //信号 线程执行完发送 } void MyThread::run() { // 实现 -- 复杂的处理过程 emit isDome; // 发送线程 }; 2.定义线程 MyThread thread; 3.开启线程

  • Qt5.9继承QObject创建多线程实例

    本博客主要总结Qt中创建多线程的另一种方法,不是通过继承类QThread,而是通过继承对象QObject,来实现多线程.(可以直接跳过下面内容,看1.1内容) 利用继承QObject方法创建多线程,主要的步骤有一下几点:(注意:退出线程循环后,还要调用QThread::quit()函数,该线程才会触发QThread::finished()信号) a1:首先创建一个类MyThread,基类为QObject. a2:在类MyThread中创建一个槽函数,用于运行多线程里面的代码.所有耗时代码,全部在

  • Qt实现UDP多线程数据处理及发送的简单实例

    逻辑与运行 程序逻辑图如下: 接收端运行截图如下: 客户端接收数据如下: 客户端用的是串口调试工具: 源码 程序结构如下: 源码如下: data.h #ifndef DATA_H #define DATA_H #include <QObject> #include <QHostAddress> #include <QString> #include <QDebug> #define SenderListWidget 0 #define ReceviListW

  • Qt线程池QThreadPool的使用详解

    目录 一.目的 二.最优线程数 三.线程池的原理 四.QThreadPool线程池 五.QThreadPool简单示例 一.目的   现在所有的高性能服务器程序,几乎都会使用到线程池技术,从而更好且有效的榨干服务器性能.而创建并销毁线程的过程势必会消耗内存.而在日常开发中内存资源是及其宝贵的,所以QT 多线程之线程池QThreadPool就有很大用处了.它可以用来管理线程的优先顺序,防止创建过多的线程,起到很好的管理作用. 二.最优线程数   线程的创建和销毁是有性能开销的,当我们有少量业务需要

  • java并发编程_线程池的使用方法(详解)

    一.任务和执行策略之间的隐性耦合 Executor可以将任务的提交和任务的执行策略解耦 只有任务是同类型的且执行时间差别不大,才能发挥最大性能,否则,如将一些耗时长的任务和耗时短的任务放在一个线程池,除非线程池很大,否则会造成死锁等问题 1.线程饥饿死锁 类似于:将两个任务提交给一个单线程池,且两个任务之间相互依赖,一个任务等待另一个任务,则会发生死锁:表现为池不够 定义:某个任务必须等待池中其他任务的运行结果,有可能发生饥饿死锁 2.线程池大小 注意:线程池的大小还受其他的限制,如其他资源池:

  • java 可重启线程及线程池类的设计(详解)

    了解JAVA多线程编程的人都知道,要产生一个线程有两种方法,一是类直接继承Thread类并实现其run()方法:二是类实现Runnable接口并实现其run()方法,然后新建一个以该类为构造方法参数的Thread,类似于如下形式: Thread t=new Thread(myRunnable).而最终使线程启动都是执行Thread类的start()方法. 在JAVA中,一个线程一旦运行完毕,即执行完其run()方法,就不可以重新启动了.此时这个线程对象也便成了无用对象,等待垃圾回收器的回收.下次

  • Java线程池FutureTask实现原理详解

    前言 线程池可以并发执行多个任务,有些时候,我们可能想要跟踪任务的执行结果,甚至在一定时间内,如果任务没有执行完成,我们可能还想要取消任务的执行,为了支持这一特性,ThreadPoolExecutor提供了 FutureTask 用于追踪任务的执行和取消.本篇介绍FutureTask的实现原理. 类视图 为了更好的理解FutureTask的实现原理,这里先提供几个重要接口和类的结构,如下图所示: RunnableAdapter ThreadPoolExecutor提供了submit接口用于提交任

  • Java多线程之线程池七个参数详解

    ThreadPoolExecutor是JDK中的线程池实现,这个类实现了一个线程池需要的各个方法,它提供了任务提交.线程管理.监控等方法. 下面是ThreadPoolExecutor类的构造方法源码,其他创建线程池的方法最终都会导向这个构造方法,共有7个参数:corePoolSize.maximumPoolSize.keepAliveTime.unit.workQueue.threadFactory.handler. public ThreadPoolExecutor(int corePoolS

  • Java 线程池全面总结与详解

    目录 原理 阻塞队列 有界阻塞队列 无界阻塞队列 同步移交队列 实现类分析 使用Executors创建线程池 线程池关闭 线程池是很常用的并发框架,几乎所有需要异步和并发处理任务的程序都可用到线程池. 使用线程池的好处如下: 降低资源消耗:可重复利用已创建的线程池,降低创建和销毁带来的消耗: 提高响应速度:任务到达时,可立即执行,无需等待线程创建: 提高线程的可管理性:线程池可对线程统一分配.调优和监控. 原理 线程池的原理非常简单,这里用处理流程来概括: 线程池判断核心池里的线程是否都在执行任

  • java线程池ThreadPoolExecutor类使用详解

    在<阿里巴巴java开发手册>中指出了线程资源必须通过线程池提供,不允许在应用中自行显示的创建线程,这样一方面是线程的创建更加规范,可以合理控制开辟线程的数量:另一方面线程的细节管理交给线程池处理,优化了资源的开销.而线程池不允许使用Executors去创建,而要通过ThreadPoolExecutor方式,这一方面是由于jdk中Executor框架虽然提供了如newFixedThreadPool().newSingleThreadExecutor().newCachedThreadPool(

  • Golang WorkerPool线程池并发模式示例详解

    目录 正文 处理CVS文件记录 获取测试数据 线程池耗时差异 正文 Worker Pools 线程池是一种并发模式.该模式中维护了固定数量的多个工作器,这些工作器等待着管理者分配可并发执行的任务.该模式避免了短时间任务创建和销毁线程的代价. 在 golang 中,我们使用 goroutine 和 channel 来构建这种模式.工作器 worker 由一个 goroutine 定义,该 goroutine 通过 channel 获取数据. 处理CVS文件记录 接下来让我们通过一个例子,来进一步理

  • Java实现手写线程池实例并测试详解

    前言 在之前的文章中介绍过线程池的核心原理,在一次面试中面试官让手写线程池,这块知识忘记的差不多了,因此本篇文章做一个回顾. 希望能够加深自己的印象以及帮助到其他的小伙伴儿们 在线程池核心原理篇介绍过线程池的核心原理,今天来模拟线程池和工作队列的流程,以及编写代码和测试类进行测试.下面附下之前线程池的核心流程: 在线程池核心原理的源码中,涉及到了一系列的流程,包括线程池队列数量是否已满,运用什么样的拒绝策略等.在我们手写线程池的代码中,不需要考虑那么多因素,只需要模拟简单的情景和过程,因此整体来

  • Java线程池使用与原理详解

    线程池是什么? 我们可以利用java很容易创建一个新线程,同时操作系统创建一个线程也是一笔不小的开销.所以基于线程的复用,就提出了线程池的概念,我们使用线程池创建出若干个线程,执行完一个任务后,该线程会存在一段时间(用户可以设定空闲线程的存活时间,后面会介绍),等到新任务来的时候就直接复用这个空闲线程,这样就省去了创建.销毁线程损耗.当然空闲线程也会是一种资源的浪费(所有才有空闲线程存活时间的限制),但总比频繁的创建销毁线程好太多. 下面是我的测试代码 /* * @TODO 线程池测试 */ @

随机推荐