简述
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 模块提供了处理一些常见并行计算模式的高级函数:map、filter 和 reduce。与使用 QThread 和 QRunnable 不同,这些函数不需要使用低级线程原语,例如:互斥锁或信号量。相反,他们返回一个 QFuture 对象,当他们准备就绪时,可用于检索函数的结果。QFuture 也可以用于查询计算进度并暂停/恢复/取消计算。为方便起见,QFutureWatcher 通过信号和槽与 QFutures 进行交互。
Qt Concurrent 的 map、filter 和 reduce 算法在所有可用的处理器核心之间自动分配计算,因此今天编写的应用程序将在以后在具有更多核心的系统上部署时继续扩展。
这个模块还提供了 QtConcurrent::run() 函数,它可以在另一个线程中运行任何函数。但是,QtConcurrent::run() 只支持可用于 map、filter 和 reduce 函数的一个子集。QFuture 可以用于检索函数的返回值,并检查线程是否正在运行。但是,对 QtConcurrent::run() 的调用仅使用一个线程,不能被暂停/恢复/取消,并且不能被查询进度。
有关各个功能的详细信息,请参阅 Qt Concurrent 模块文档。
WorkerScript - QML 中的线程
WorkerScript QML 类型允许 JavaScript 代码与 GUI 线程并行运行。
每个 WorkerScript 实例可以有一个 .js 脚本附加到它。当调用 WorkerScript::sendMessage() 时,脚本将在单独的线程(和单独的 QML 上下文)中运行。当脚本完成运行时,它可以发送一个回复给 GUI 线程,它将调用 WorkerScript::onMessage() 信号处理程序。
使用 WorkerScript 类似于使用已移动到另一个线程的工作 QObject。 数据通过信号在线程之间传输。
有关如何实现脚本以及可以在线程之间传递的数据类型的列表的详细信息,请参阅 WorkerScript 文档。
选择适当的方法
如上所述,Qt 为开发线程应用程序提供了不同的解决方案。给定应用程序的正确解决方案取决于新线程的用途和线程的生命周期。下面是 Qt 的线程技术的比较,接下来是一些示例用例的推荐解决方案。
解决方案的比较
特性 | QThread | QRunnable 和 QThreadPool | QtConcurrent::run() | Qt Concurrent (Map, Filter, Reduce) | WorkerScript |
---|---|---|---|---|---|
语言 | C++ | C++ | C++ | C++ | QML |
可以指定线程优先级 | 是 | 是 | |||
线程可以运行事件循环 | 是 | ||||
线程可以通过信号接收数据更新 | 是(由 worker QObject 接收) | 是(由 WorkerScript 接收) | |||
线程可以使用信号控制 | 是(由 QThread 接收) | 是(由 QFutureWatcher 接收) | |||
线程可以通过 QFuture 监视 | 部分 | 是 | |||
内置的暂停/恢复/取消功能 | 是 |
示例用例
生命周期 | 操作 | 解决方案 |
---|---|---|
一次调用 | 在另一个线程中运行一个新的线性函数,可选择在运行过程中更新进度。 | Qt 提供了不同的解决方案: 1. 将函数放在 QThread::run() 的重新实现中,并启动 QThread,发射信号来更新进度。 2. 将函数放在 QRunnable::run() 的重新实现中,并将 QRunnable 添加到 QThreadPool,写入线程安全的变量来更新进度。 3. 使用 QtConcurrent::run() 运行函数,写入线程安全的变量来更新进度。 |
一次调用 | 在另一个线程中运行现有函数并获取其返回值 | 使用 QtConcurrent::run() 运行函数,当函数返回时,有一个 QFutureWatcher 发出 finished() 信号,并调用 QFutureWatcher::result() 获取函数的返回值。 |
一次调用 | 使用所有可用内核对容器的所每一项执行操作。例如,从图像列表中生成缩略图。 | 使用 Qt Concurrent 的 QtConcurrent::filter() 函数来选择容器元素,并使用 QtConcurrent::map() 函数来对每个元素应用一个操作。若要将输出折叠成单一的结果,改用 QtConcurrent::filteredReduced() 和QtConcurrent::mappedReduced() 来代替。 |
一次调用/持久运行 | 在纯 QML 应用程序中执行长计算,并在结果准备好时更新 GUI。 | 将计算代码放在 .js 脚本中,并将其附加到 WorkerScript 实例。调用 sendMessage() 在一个新的线程中启动计算,也让脚本调用 WorkerScript::sendMessage(),将结果传递给 GUI 线程,处理 onMessage 中的结果并更新 GUI。 |
持久运行 | 有一个对象生活在另一个线程中,可以根据请求执行不同的任务和/或可以接收新的数据。 | 子类化一个 QObject 以创建 worker。实例化这个 worker 对象和一个 QThread,将 worker 移动到新线程,通过 queued 信号槽连接将命令或数据发送到 worker 对象。 |
持久运行 | 在另一个线程中重复执行昂贵的操作,其中线程不需要接收任何信号或事件。 | 在 QThread::run() 的重新实现中直接编写无限循环,启动没有事件循环的线程,让线程发出信号将数据发送回 GUI 线程。 |