目录
Qt提供了许多用于处理线程的类和函数。 Qt程序员可以使用以下四种不同的方法来实现多线程应用程序。
QThread:具有可选事件循环的低级API
QThread是Qt中所有线程控制的基础。 每个QThread实例代表并控制一个线程。
QThread可以直接实例化也可以子类化。 实例化QThread提供了一个并行事件循环,从而允许在辅助线程中调用QObject槽。 子类化QThread允许应用程序在开始其事件循环之前初始化新线程,或者在没有事件循环的情况下运行并行代码。
有关如何使用QThread的演示,请参见QThread类参考和线程示例。
QThreadPool和QRunnable:重用线程
频繁创建和销毁线程开销可能会比较大。为了减少这种开销,可以将现有线程重用于新任务。 QThreadPool是可重用的QThreads的集合。
要在QThreadPool的线程之一中运行代码,请重新实现QRunnable::run()并实例化子类QRunnable。使用QThreadPool :: start()将QRunnable放入QThreadPool的运行队列中。当线程可用时,QRunnable::run()中的代码将在该线程中执行。
每个Qt应用程序都有一个全局线程池,可通过QThreadPool::globalInstance()访问该线程池。该全局线程池根据CPU中的内核数自动维护最佳线程数。但是,可以显式创建和管理单独的QThreadPool。
Qt Concurrent:使用高级API
Qt Concurrent模块提供了高级功能,可处理一些常见的并行计算模式:映射,过滤和归约。
与使用QThread和QRunnable不同,这些函数从不需要使用低级线程原语,例如互斥量或信号量。相反,它们返回一个QFuture对象,当准备就绪时,该对象可用于检索函数的结果。 QFuture还可以用于查询计算进度以及暂停/恢复/取消计算。为方便起见,QFutureWatcher允许通过信号和槽与QFutures进行交互。
Qt Concurrent的映射,过滤和归约算法会自动将计算分配到所有可用的处理器内核中,因此,当稍后在具有更多内核的系统上部署时,今天编写的应用程序将继续扩展。
该模块还提供了QtConcurrent::run()函数,该函数可以在另一个线程中运行任何函数。但是,QtConcurrent::run()仅支持可用于映射,过滤和归约功能的功能子集。 QFuture可用于检索函数的返回值并检查线程是否正在运行。但是,对QtConcurrent::run()的调用仅使用一个线程,不能暂停/继续/取消,也不能查询进度。
有关各个功能的详细信息,请参见Qt Concurrent模块文档。
WorkerScript:QML中的线程
WorkerScript QML类型允许JavaScript代码与GUI线程并行运行。
每个WorkerScript实例可以附加一个.js脚本。调用WorkerScript.sendMessage()时,脚本将在单独的线程(和单独的QML上下文)中运行。脚本运行完毕后,可以将答复发送回GUI线程,该线程将调用WorkerScript.onMessage()信号处理程序。
使用WorkerScript类似于使用已经移到另一个线程的worker QObject。数据通过信号在线程之间传输。
有关如何实现脚本的详细信息以及可以在线程之间传递的数据类型的列表,请参见WorkerScript文档。
选择适当的方法
如上所述,Qt为开发多线程应用程序提供了不同的解决方案。给定应用程序的正确解决方案取决于新线程的目的和线程的生存期。下面是Qt线程技术的比较,然后是一些用例的推荐解决方案。
解决方案对比
方案 | QThread | QRunnable and QThreadPool | QtConcurrent::run() | Qt Concurrent (Map, Filter, Reduce) | WorkerScript |
编程语言 | C++ | C++ | C++ | C++ | QML |
可以指定线程优先级 | 是 | 是 | |||
线程可以运行事件循环 | 是 | ||||
线程可以通过信号接收数据更新 | 是 (received by a worker QObject) | 是 (received by WorkerScript) | |||
可以使用信号控制线程 | 是 (received by QThread) | 是 (received by QFutureWatcher) | |||
可以通过QFuture监视线程 | 部分 | 是 | |||
内置的暂停/恢复/取消功能 | 是 |
使用示例
线程寿命 | 操作方式 | 解决方案 |
一次性调用 | 在另一个线程中运行新的线性函数,可以选择在运行期间更新进度。 | Qt提供了不同的解决方案: 1.将函数放在重载的QThread::run()中,然后启动QThread。发出信号以更新进度。 2.将函数放在QRunnable::run()的重载实现中,并将QRunnable添加到QThreadPool。写入线程安全变量以更新进度。 3.使用QtConcurrent::run()运行该函数。写入线程安全变量以更新进度。 |
一次性调用 | 在另一个线程中运行一个现有函数并获取其返回值。 | 使用QtConcurrent::run()运行该函数。让函数返回时让QFutureWatcher发出finish()信号,然后调用QFutureWatcher :: result()以获取函数的返回值。 |
一次性调用 | 使用所有可用的内核对容器的所有项目执行操作。 例如,从图像列表生成缩略图。 | 使用Qt Concurrent的QtConcurrent::filter()函数选择容器元素,并使用QtConcurrent::map()函数将操作应用于每个元素。要将输出折叠为单个结果,请改用QtConcurrent::filteredReduced()和QtConcurrent::mappedReduced()。 |
一次性调用/长期调用 | 在纯QML应用程序中进行长时间的计算,并在准备好结果后更新GUI。 | 将计算代码放在.js脚本中,并将其附加到WorkerScript实例。调用WorkerScript.sendMessage()以在新线程中开始计算。让脚本也调用sendMessage(),以将结果传递回GUI线程。在onMessage中处理结果并在那里更新GUI。 |
长期调用 | 使对象驻留在另一个线程中,该对象可以根据请求执行不同的任务和/或可以接收要使用的新数据。 | 子类化QObject以创建工作器。实例化此工作对象和一个QThread。将工作程序移动到新线程。通过排队的信号槽连接将命令或数据发送到辅助对象。 |
长期调用 | 在另一个线程中重复执行耗时的操作。该线程不需要该线程接收任何信号或事件。 | 直接在重新实现QThread :: run()内编写无限循环。启动线程而没有事件循环。让线程发出信号以将数据发送回GUI线程。 |