文章目录
1. 简介
QtConcurrent
命名空间提供了高级 api,使得无需使用诸如互斥、读写锁、等待条件或信号量等低级线程原语就可以编写多线程程序。使用 QtConcurrent
编写的程序会根据可用的线程处理器核心数量自动调整使用的线程数,这意味着编写的应用程序在部署到多核系统时将自动扩展。
当你发现你自己的程序UI运行不流畅时可以尝试将执行计算的函数放到QtConcurrent::run()
中处理,这比改用QThread
方便得多。
对于不同的需求可以参照下表:
生命周期 | 开发任务 | 解决方案 |
---|---|---|
一次调用 | 在另一个线程中运行一个函数,函数完成时退出线程 | 1.编写函数,使用QtConcurrent::run 运行它2.派生 QRunnable ,使用QThreadPool::globalInstance()->start() 运行它3.派生 QThread ,重新实现QThread::run() ,使用QThread::start() 运行它 |
一次调用 | 需要操作一个容器中所有的项。使用处理器所有可用的核心。一个常见的例子是从图像列表生成缩略图。 | QtConcurrent 提供了map() 函你数来将操作应用到容器中的每一个元素,提供了fitler() 函数来选择容器元素,以及指定reduce 函数作为选项来组合剩余元素。 |
一次调用 | 一个耗时运行的操作需要放入另一个线程。在处理过程中,状态信息需要发送会GUI线程。 | 使用QThread ,重新实现run 函数并根据需要发送信号。使用信号槽的queued 连接方式将信号连接到GUI线程的槽函数。 |
持久运行 | 生存在另一个线程中的对象,根据要求需要执行不同的任务。这意味着工作线程需要双向的通讯。 | 派生一个QObject 对象并实现需要的信号和槽,将对象移动到一个运行有事件循环的线程中并通过queued 方式连接的信号槽进行通讯。 |
持久运行 | 生存在另一个线程中的对象,执行诸如轮询端口等重复的任务并与GUI线程通讯。 | 同上,但是在工作线程中使用一个定时器来轮询。尽管如此,处理轮询的最好的解决方案是彻底避免它。有时QSocketNotifer 是一个替代。 |
多线程的一些替代技术(搬运):
替代技术 | 注解 |
---|---|
QEventLoop::processEvents() |
在一个耗时的计算操作中反复调用QEventLoop::processEvents() 可以防止界面的假死。尽管如此,这个方案可伸缩性并不太好,因为该函数可能会被调用地过于频繁或者不够频繁。 |
QTimer |
后台处理操作有时可以方便地使用Timer安排在一个在未来的某一时刻执行的槽中来完成。在没有其他事件需要处理时,时间隔为0的定时器超时事件被相应 |
QSocketNotifier QNetworkAccessManager QIODevice::readyRead() |
这是一个替代技术,替代有一个或多个线程在慢速网络执行阻塞读的情况。只要响应部分的计算可以快速执行,这种设计比在线程中实现的同步等待更好。与线程相比这种设计更不容易出错且更节能(energy efficient)。在许多情况下也有性能优势。 |
2. 准备工作
2.1 修改.pro
文件
使用 QtConcurrent
模块,需要在 .pro
中添加: QT += concurrent
2.2 包含头文件和声明命名空间
#include <QtConcurrent/QtConcurrent>
using namespace QtConcurrent;
3. 运行
3.1 运行外部函数
要在另一个线程中运行一个函数,可以使用 QtConcurrent: : run ()
:
extern void aFunction();
QFuture<void> future = QtConcurrent::run(aFunction);
这将从默认 QThreadPool
获得的单独线程中运行 aFunction
。可以使用 QFuture
和 QFutureWatcher
类来监视函数的状态。如果要使用专用线程池,可以将 QThreadPool
作为第一个参数传递:
extern void aFunction();
QThreadPool pool;
QFuture<void> future = QtConcurrent::run(&pool, aFunction);
3.2 运行成员函数
Run ()
也接受指向成员函数的指针。第一个参数必须是常量引用或指向类实例的指针。在调用 const 成员函数时,通常使用 const
引用传递; 通常使用指针传递对于调用修改实例的非 const
成员函数。【强烈建议拓展阅读Qt并发模块Qt Concurrent使用的血泪教训】
例如,在一个单独的线程中调用 QByteArray: : split ()
(一个 const 成员函数)是这样做的:
// call 'QList<QByteArray> QByteArray::split(char sep) const' in a separate thread
QByteArray bytearray = "hello world";
QFuture<QList<QByteArray> > future = QtConcurrent::run(bytearray, &QByteArray::split, ',');
...
QList<QByteArray> result = future.result();
调用一个非常量成员函数是这样做的:
// call 'void QImage::invertPixels(InvertMode mode)' in a separate thread
QImage image = ...;
QFuture<void> future = QtConcurrent::run(&image, &QImage::invertPixels, QImage::InvertRgba);
...
future.waitForFinished();
// At this point, the pixels in 'image' have been inverted
4. 向函数传递参数的方式
通过将参数添加到紧跟在函数名之后的 QtConcurrent::run()
调用中来完成向函数传递参数。例如:
extern void aFunctionWithArguments(int arg1, double arg2, const QString &string);
int integer = ...;
double floatingPoint = ...;
QString string = ...;
QFuture<void> future = QtConcurrent::run(aFunctionWithArguments, integer, floatingPoint, string);
运行的机制是:在调用 QtConcurrent: : run ()
的地方复制每个参数,并在线程开始执行函数时将这些值传递给线程。调用 QtConcurrent: : run ()
后对参数所做的更改对线程不可见。
5. 获取函数返回值的方式
返回值可以通过 QFuture
获得:
extern QString functionReturningAString();
QFuture<QString> future = QtConcurrent::run(functionReturningAString);
...
QString result = future.result();
注意,QFuture: : result ()
函数阻塞并等待结果可用。当函数完成执行并且结果可用时,使用 QFutureWatcher
获得通知。