Multithreading Technologies in Qt
Qt 提供一系列的类与函数来处理多线程. Qt 开发者们可以使用下面四种方法来实现多线程应用.
QThread: 低级 API 与可选的事件循环
作为 Qt 线程控制的基础, 每一个 QThread 实例都代表并控制着一个线程.
你可以直接实例化QThread, 或建立子类. 实例化一个 QThread 将附带一个并行事件循环, 允许 QObject 槽函数在子线程执行. 若子类化一个 QThread, 程序可以在事件循环启动前初始化这个新线程, 或者在无事件循环下运行并行代码.
如何使用QThread, 参见 QThread class reference 和 threading examples.
QThreadPool 和 QRunnable: 线程重用
频繁地创建与销毁线程会消耗很大的系统资源. 为了减少这样额外的开销, 可以重复使用一些现成的线程来执行新的任务. QThreadPool 就是这样一个保存着可重用的 QThread 的集合.
为了将代码放入 QThreadPool的线程中运行, 可以重写 QRunnable::run() 函数并实例化继承自 QRunnable 的子类. 调用 QThreadPool::start() 函数可将 QRunnable 添加到 QThreadPool的运行队列. 一旦出现了一个可用的线程, 它将会执行 QRunnable::run() 里的代码.
每一个 Qt 程序都会自带一个公共线程池, 可以通过调用 QThreadPool::globalInstance() 获取. 线程数为基于 CPU 核心数计算的最佳值. 不过, 你也可以显式创建并管理一个独立的 QThreadPool.
Qt Concurrent: 使用高级 API
The Qt Concurrent 块提供了数个高级函数, 用于处理一些常见的并行计算模式: map, filter, 和 reduce. 不同于使用 QThread 和 QRunnable, 这些高级函数不需要使用 底层线程原语, 比如互斥锁与信号量. 取而代之的是返回一个 QFuture 对象, 它能够在传入的函数返回值就绪后检索该结果. QFuture 既可以用来查询计算进度, 也可以暂停/恢复/取消计算. 方便起见, QFutureWatcher 可以让您通过信号槽与 QFuture 进行交互.
Qt Concurrent的 map, filter 和 reduce 算法会自动将计算过程分配到可用的处理器核心, 由此, 当下编写的程序在以后部署到更多核心的系统上时会被自动扩展.
此模块还提供了 QtConcurrent::run() 函数, 可以将任何函数在另一个线程中运行. 不过, QtConcurrent::run() 仅提供 map, filter 和 reduce 函数的一部分功能. QFuture 可以用于检索函数返回值, 也可以用于查看线程是否处于运行中. 然而, 调用 QtConcurrent::run() 时只会使用一个线程, 并且无法暂停/恢复/取消, 也不能查询计算进度.
详见 Qt Concurrent.
WorkerScript: QML 中的多线程
The WorkerScript QML 类型 WorkerScript 可将 JavaScript 代码与 GUI 线程并行运行.
每个 WorkerScript 实例可附加一个 .js
脚本. 当调用 WorkerScript::sendMessage() 时, 脚本将会运行在一个独立的线程中 (伴随一个独立的 QML 上下文). 在脚本运行结束后, WorkerScript 将会向 GUI 线程发送回复, 后者会调用 WorkerScript::onMessage() 信号处理函数.
使用 WorkerScript, 很像使用一个移入子线程工作的 QObject, 数据通过信号槽在线程间进行传输.
详见 WorkerScript.
选择合适的方法
如上文所述, Qt 提供了开发多线程应用的不同解决方案. 对一个给定的程序, 需要根据新线程的用途与线程的生命周期来决定正确的方案. 下面是一组 Qt 多线程技术的功能对比表, 以及对于一些范例较为推荐的解决方案.
解决方案对比
Feature | QThread | QRunnable and QThreadPool | QtConcurrent::run() | Qt Concurrent (Map, Filter, Reduce) | WorkerScript |
---|---|---|---|---|---|
Language | C++ | C++ | C++ | C++ | QML |
Thread priority can be specified | Yes | Yes | |||
Thread can run an event loop | Yes | ||||
Thread can receive data updates through signals | Yes (received by a worker QObject) | Yes (received by WorkerScript) | |||
Thread can be controlled using signals | Yes (received by QThread) | Yes (received by QFutureWatcher) | |||
Thread can be monitored through a QFuture | Partially | Yes | |||
Built-in ability to pause/resume/cancel | Yes |
Example Use Cases
Lifetime of thread | Operation | Solution |
---|---|---|
一次调用 | 在另一个线程中运行新的线性函数, 并选择在运行期间更新进度. | Qt 提供不同的解决方案:
|
一次调用 | 在另一个线程中运行一个已存在的函数, 并获取返回值. | 调用 QtConcurrent::run() 运行函数. QFutureWatcher 在函数结束时发出 finished() 信号, 调用 QFutureWatcher::result() 获取函数运行结果. |
一次调用 | 对容器中的元素执行统一操作, 如, 对一组图形生成缩略图. | 使用 Qt Concurrent 的 QtConcurrent::filter() 函数选择容器元素, 使用 QtConcurrent::map() 函数对每个元素执行统一操作. 使用 QtConcurrent::filteredReduced() 和 QtConcurrent::mappedReduced() 输出一个简单的处理结果. |
一次调用/常驻 | 在纯 QML 应用程序中执行长时间计算, 并在结果准备好时更新 GUI. | 将计算代码放入 .js 脚本中并将其附加到 WorkerScript 实例. 调用 WorkerScript::sendMessage() 在新线程中开始计算; 脚本也调用 WorkerScript::sendMessage() 将结果传递回 GUI 线程. 在 onMessage 中处理结果并更新 GUI. |
常驻 | 让一个对象存在于另一个线程中, 该对象可以根据请求执行不同的任务和/或可以接收要使用的新数据. | 子类化 QObject, 创建一个工作者. 实例化这个工作对象和一个 QThread 对象. 将工作对象移入新线程. 通过信号和槽函数的连接队列发送命令或数据. |
常驻 | 在另一个线程中重复执行耗时操作, 该线程不需要接收任何信号或事件. | 重新实现 QThread::run() 实现这个操作. 启动没有事件循环的线程, 线程使用信号将数据发给 GUI 线程. |