QT 多线程 线程池 QRunnalbe QThreadPool

本文围绕QT中的线程池展开,介绍了线程池的作用及使用原因,阐述了QRunnable和QThreadPool相关类,说明了全局线程池的使用,讲解了基本运用、自定义信号槽等内容,还对比了QThread与QRunnable+QThreadPool适用场景,并给出QRunnable与外界通信的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、背景

        QT中线程很早就出现了,多线程的使用目的是为了减轻主线程压力,不至于主线程界面卡顿,提高用户体验。但是线程的创建与销毁需要与系统交互,会产生很大的开销。若需要频繁的创建线程就建议使用线程池,有线程池维护一定数量的线程,当需要进行多线程的运算时,将运算函数传递给线程池即可。线程池会根据可用线程进行任务安排。

线程池有什么作用呢?

一个作用就是限制系统中执行线程的数量。根据系统的环境情况,可以自动或手动设置线程数量,达到运行的最佳效果;少了浪费了系统资源,多了造成系统拥挤效率不高。用线程池控制线程数量,其他线程排队等候。一个任务执行完毕,再从队列的中取最前面的任务开始执行。若队列中没有等待进程,线程池的这一资源处于等待。当一个新任务需要运行时,如果线程池中有等待的工作线程,就可以开始运行了;否则进入等待队列。

为什么要是用线程池?

1.减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
2.可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。

二、相关类

在QT中使用多线程,可以使用QThread。但是如果使用线程池可以使用QRunnable + QThreadPool 

QRunnable 是所有的runnable对象的基类,是一个接口,用于表示一个任务或者要执行的代码,需要重新实现void run()函数。

QThreadPool类用于管理QThreads集合,管理和循环使用单独的QThread对象,以帮助程序减少创建线程的成本。每一个QT应用程序都有一个全局的QThreadPool对象,可以通过globalInsatance()获取。

QThreadPool *thradPool = QThreadPool::globalInstance();//获取全程线程池

1.QRunnable :

所有的需要在子线程进行的计算或者任务,都需要在继承于QRunnable的类里,然后重新实现void run()函数。最后生成一个对象runnableObj,扔给线程池。

QThreadPool::globalInstance()->start(runnableObj);//扔给线程池

类成员函数:

void setAutoDelete(bool autoDelete);//设置是否自动删除。默认是自动删除。运行完后,由线程池自动删除对象。

2.QThreadPool:线程池类

类成员函数:

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

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

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

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)//尝试启动一个任务,成功就返回true

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

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

三、一些要解释的事情

  • QThread::idealThreadCount 函数,会根据当前设备的硬件情况给出一个默认的线程数量,而线程池的默认最大线程数就是此值,maxThreadCount.
  • 由于reserveThread 后的线程不计入线程最大数量;因此,可能出现activeThreadCount > maxThreadCount的情况。
  • QThreadPool中的start()函数里,传入的是QRunnable对象指针,传入后,线程池会调用QRunnable的autoDelete()函数。若返回true,则当此运算完成后自动释放内容、空间,不需要后续主动判断是否完成并释放空间。
  • tryStart(),若返回成功,不会自动释放内容,需要调用方主动释放,无论aotudelete返回值是什么。返回false,也不会主动delete
  • 对于tryStart() 如果返回成功,则等同于start(). 若返回false,则不会自动delete.
  • 对于autoDelete必须在start/tryStart之前设置,不要在调用之后修改,否则结果不可预测。

四、全局线程池

QThreadPool,提供一个静态函数,QThreadPool::globalInstance(),返回一个当前进程的全局线程池,可在多个类中共同使用一个线程池。也可以新建一个线程池。

五、基本运用

要使用线程池,先要子类化一个QRunnable并实现run()虚函数。然后创建一个对象,并把它传给QThreadPool::start(),这会把可运行对象的拥有权赋权给QT的全局线程池,并让他可以开始运行。

class HelloWorldTask : public QRunnable
{
    void run() {
        qDebug() << "Hello world from thread " << QThread::currentThread();
    }
}
 
HelloWorldTask *hello = new HelloWorldTask();
 
// QThreadPool取得所有权,并自动删除 hello

QThreadPool::globalInstance()->start(hello);

默认情况下,可运行对象结束时,线程池会自动将其删除,这也正是我们想要的结果。在某些情况下,如果必须有我们自己负责删除可运行对象时,可以通过调用QRunnable::setAutoDelete(false)来阻止自动删除的发生。

六、自定义信号槽

打开QRunnable的头文件时,我们会发现,它不是继承于QObject,也就是说无法使用QObject的特性,例如,信号槽,事件等,为了方便使用,我们可以继承QObject:


class HelloWorldTask : public QObject, public QRunnable
{
    Q_OBJECT
 
// 自定义信号
signals:
    void finished();
 
public:
     void run() {
         qDebug() << "Hello Thread : " << QThread::currentThreadId();
         emit finished();
     }
};

使用时,连接信号槽就行:


class MainWindow : public QMainWindow
{
    Q_OBJECT
 
public:
    explicit MainWindow(QWidget *parent = 0)
        : QMainWindow(parent)
    {
        qDebug() << "Main Thread : " << QThread::currentThreadId();
 
        // ...
        HelloWorldTask *hello = new HelloWorldTask();
        connect(hello, SIGNAL(finished()), this, SLOT(onFinished()));
        QThreadPool::globalInstance()->start(hello);
        // ...
    }
 
protected:
    void closeEvent(QCloseEvent *event) {
        if (QThreadPool::globalInstance()->activeThreadCount())
            QThreadPool::globalInstance()->waitForDone();
 
        event->accept();
    }
 
private slots:
    void onFinished() {
        qDebug() << "SLOT Thread : " << QThread::currentThreadId();
    }
};

七、QThread与QRunnable+QThreadPool适用的场景

        QThread是QT的线程类,通过继承QThread然后重写run函数即可实现一个线程类。QThreadPool+ QRunnable配合构建线程池的方法也可以实现线程。我们通过以下问题对上述两种构建线程的方法进行分析和说明。

    (1)既然QThread这么简单我们为什么还要使用QThreadPool + QRunnable创建线程池的方法来使用线程机制呢?

主要原因:当线程任务量非常大的时候,如果频繁的创建和释放QThread会带来比较大的内存开销,而线程池则可以有效避免该问题,相关的基础支持可以自行百度线程池的优点。

    (2)QThread与 QThreadPool + QRunnable分别适用于哪种应用场景?

     QThread适用于那些常驻内存的任务。而且QThread可以通过信号/槽的方式与外界进行通信。而QRunnable则适用于那些不常驻内存,任务数量比较多的情况。

 

八、QRunnable 如何与外界进行通信

       方法1:QRunnable并不继承自QObject类,因此无法使用信号/槽的方式与外界进行通信。我们就必须的使用其他方法,这里给大家介绍的是使用:QMetaObject::invokeMethod()函数。

       方法2:使用多重继承的方法,任务子类同时继承自QRunnable和QObject。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值