Qt之QThreadPool和QRunnable

简介: 简述QRunnable 是所有 runnable 对象的基类,而 QThreadPool 类用于管理 QThreads 集合。QRunnable 类是一个接口,用于表示一个任务或要执行的代码,需要重新实现 run() 函数。QThreadPool 管理和循环使用单独的 QThread 对象,以帮助程序减少创建线程的成本。每个 Qt 应用程序都有一个全局 QThre

简述

QRunnable 是所有 runnable 对象的基类,而 QThreadPool 类用于管理 QThreads 集合。

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

QThreadPool 管理和循环使用单独的 QThread 对象,以帮助程序减少创建线程的成本。每个 Qt 应用程序都有一个全局 QThreadPool 对象,可以通过调用 globalInstance() 访问。

详细描述

QThreadPool 支持多次执行相同的 QRunnable,通过调用 QThreadPool::tryStart(this) 从 run() 函数内。如果启用了 autoDelete,当最后一个线程退出 run() 函数,QRunnable 将被删除。多次调用 QThreadPool::start() 使用相同的 QRunnable,当启用 autoDelete 时会创建一个竞争条件,不推荐使用。

一定时间未使用线程将会到期,默认到期超时是 30000 毫秒(30秒)。可以使用 setExpiryTimeout() 来改变,设定一个负值,则会禁用到期机制。

调用 maxThreadCount() 查询使用线程的最大数量,如果需要,可以使用 setMaxThreadCount() 进行更改。默认情况下,maxThreadCount() 是 QThread::idealThreadCount()。activeThreadCount() 函数返回当前正在工作线程的数量。

注意: QThread::idealThreadCount() 提供了计算程序运行所在平台上支持的辅助线程的最佳数量 - 考虑到操作系统、处理器的数量和机器拥有的处理核的数量。对于只有一个处理器、一个处理核的机器,该函数或许会返回 1。对于拥有多个处理器和处理核的机器,返回值则会相应增大,这个数字不一定会与需要处理的文件个数完全匹配,因此需要将任务划分,这样的话,每个辅助线程(假设使用的辅助线程大于 1)都能得到一个和需要处理的文件数量相等的数值(当然,用文件数量目来划分任务或许不是在所有情况下都是最好的方法,例如:在一个数量为 20 的文件列表中,前 10 个文件很大,后 10 个 文件很小)。

reserveThread() 函数储备一个线程用于外部使用。当线程完成后,使用 releaseThread(),以便它可以被重新使用。从本质上讲,这些函数暂时增加或减少活跃线程的数量,并且当实现耗时的操作时对 QThreadPool 是不可见的,这比较有用。

注意: QThreadPool 是一个管理线程的低级类,高级替代品可以用 Qt Concurrent 模块。

基本使用

要使用 QThreadPool 的一个线程,子类化 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();
    }
};

为了获得安全清楚,对于多线程应用程序来说,最好在终止程序之前,停止所有辅助线程。我们已经通过 closeEvent() 做到了这一点,可以确保在允许终止动作之前让任何活动的线程先结束掉。

顺便再介绍下所属线程,使用 qDebug 将各自的线程 ID 进行调试输出。结果如下:

Main Thread : 0xb308
Hello Thread : 0xb33c
SLOT Thread : 0xb308

显然,槽函数所在线程与主线程相同。

如果想要槽函数在次线程中执行,只需改变信号槽的连接方式:

connect(hello, SIGNAL(finished()), this, SLOT(onFinished()), Qt::DirectConnection);

这时,就得到了想要的结果啦:

Main Thread : 0xb030
Hello Thread : 0xacf8
SLOT Thread : 0xacf8

事件的传递、数据的交互方式很多,这里只介绍了信号槽。当然还可以使用自定义事件,然后通过 调用 QApplication::sendEvent() 或 QApplication::postEvent() 发送;或者使用 QMetaObject::invokeMethod() 方式均可,这里就不再赘述了。

更多参考

目录
相关文章
|
缓存 Java
《QT从基础到进阶·二十》QThreadPool线程池的使用
《QT从基础到进阶·二十》QThreadPool线程池的使用
855 0
|
4月前
|
数据安全/隐私保护 C++ 计算机视觉
Qt(C++)开发一款图片防盗用水印制作小工具
文本水印是一种常用的防盗用手段,可以将文本信息嵌入到图片、视频等文件中,用于识别和证明文件的版权归属。在数字化和网络化的时代,大量的原创作品容易被不法分子盗用或侵犯版权,因此加入文本水印成为了保护原创作品和维护知识产权的必要手段。 通常情况下,文本水印可以包含版权声明、制作者姓名、日期、网址等信息,以帮助识别文件的来源和版权归属。同时,为了增强防盗用效果,文本水印通常会采用字体、颜色、角度等多种组合方式,使得水印难以被删除或篡改,有效地降低了盗用意愿和风险。 开发人员可以使用图像处理技术和编程语言实现文本水印的功能,例如使用Qt的QPainter类进行文本绘制操作,将文本信息嵌入到图片中,
181 1
Qt(C++)开发一款图片防盗用水印制作小工具
|
3月前
|
监控 C++ 容器
【qt】MDI多文档界面开发
【qt】MDI多文档界面开发
89 0
|
2月前
|
开发工具 C++
qt开发技巧与三个问题点
本文介绍了三个Qt开发中的常见问题及其解决方法,并提供了一些实用的开发技巧。
|
2月前
|
3月前
|
C++
C++ Qt开发:QUdpSocket网络通信组件
QUdpSocket是Qt网络编程中一个非常有用的组件,它提供了在UDP协议下进行数据发送和接收的能力。通过简单的方法和信号,可以轻松实现基于UDP的网络通信。不过,需要注意的是,UDP协议本身不保证数据的可靠传输,因此在使用QUdpSocket时,可能需要在应用层实现一些机制来保证数据的完整性和顺序,或者选择在适用的场景下使用UDP协议。
158 2
Qt开发网络嗅探器02
Qt开发网络嗅探器02
|
3月前
|
存储 运维 监控
Qt开发网络嗅探器01
Qt开发网络嗅探器01
|
3月前
|
网络协议 容器
Qt开发网络嗅探器03
Qt开发网络嗅探器03
|
3月前
【qt】多窗口开发
【qt】多窗口开发
60 0