https://doc.qt.io/qt-5/threads-technologies.html
Qt 提供了很多类和方法用于处理多线程。Qt 程序员可以使用下面四种不同的方式实现多线程应用。
QThread: 带有可选择事件循环的低级API
QThread是Qt中所有线程控制的基础。每一个QThread实例代表和控制一个线程。
QThread既可以直接实例化,也可以作为一个子类。实例化一个QThread,可以提供一个并行的事件循环,允许在一个次要的线程中调用QObject的槽。子类化一个QThread可以使应用在其本身事件循环开始前去实例一个新线程,或者在没有事件循环情况下,运行并行代码。
关于怎样使用QThread 详细示范请查看 QThread class reference 和 threading examples
QThreadPool 和 QRunnable: 可重用线程
频繁创建销毁线程开销特别大。为减少内存消耗,新的任务可以使用空闲线程。QThreadPool就是可利用线程的集合。
要在一个QThreadPool的线程中运行代码,需要重新实现QRunnable::run()并实例化子类QRunnable。用QThreadPool::start()把QRunnable放进QThreadPool的运行队列。当一个线程可用时,QRunnable::run()中的代码将在该线程中执行。
每一个Qt应用都有一个全局的线程池,可以使用QThreadPool::globalInstance()访问。这个全局线程池会根据CPU的内核数量自动维护一个最优的线程数量。然而,可以显示创建和管理一个单独的QThreadPool。
Qt 并发: 高级API
Qt Concurrent 模块提供了一些常见并行计算模版化的高级函数:map,filter,和 reduce。与Qyhread和QRunnable不同,这些高级函数不需要使用类似互斥锁、信号量,这些低级线程原语。相反,这些函数会返回一个QFuture对象,当函数准备好时,会检索函数的结果。QFuture还可以用于查询计算进度,暂停/重置/取消 计算。为了方便,QFutureWadtcher可是通过信号和槽实现QFuture间的交互。
Qt Concurrent的map,filter,和 reduce 算法会自动分布到所有可利用的处理器内核上,所以现在编写的应用程序在以后部署更多内核时会继续扩展。
这个模块还提供QtConcurrent::run()方法,可以在另一个线程运行任何方法。然而QtConcurrent::run()只提供map,filter,reduce 函数可用属性子集。QFuture可以用于检索函数返回值并检查线程是否仍在运行。但是,调用QtConcurrent::run()只使用一个线程,不能 暂停/重置/取消,和查询进度操作。
有关Qt Concurrent模块独特方法的使用细节,请查阅文档Qt Concurrent 。
WorkerScript: QML 中的线程化
这个WorkerScript QML线程可以使JavaScript代码 和 GUI线程平行化执行。
每一个WorkerScript实例都可以附加一个.js 脚本。当调用 WorkerScript.sendMessage()函数时,脚本可以在一个隔离的线程运行(同时也有一个单独的QML执行期上下文)。当脚本完成运行,会发送一个应答给GUI线程,线程会激活WorkerScript.onMessage()的信号处理器。
使用WorkerScript就像是,用一个已经移动到另一个线程去工作的QObject。数据通过信号在线程见传输。
查阅WorkerScript文档可以了解实现的细节,和线程间传输的数据类型列表。
选择一个恰当的方式
正如上面所示范的,Qt应用为开发线程应用提供不同的解决方案。基于线程的目的和生命周期才能提供给应用程序一个最佳的方案。下面是各种线程技术对比,后面是一些示例的推荐方案。
方案对比:
特点 | QThread | QRunnable和QThreadPool | QtConcurrent::run() | Qt Concurrent(Map, Filter, Reduce) |
语言 | C++ | C++ | C++ | C++ |
可以指定线程优先级 | 是 | 是 | ||
线程可以执行一个事件循环 | 是 |
| ||
线程可以接收信号更新数据 | 是(通过一个worker QObject接收) |
| ||
线程可以使用信号控制 | 是(通过QThread接收) |
| 是(通过QFutureWatcher接收) | |
线程可以通过QFuture来监视 | 一部分 | 是 | ||
内置功能可以暂停/重置/取消 |
| 是 |
示范用例:
线程生命周期 | 操作 | 解决方案 |
一次性 | 在另一个线程中执行一个新的线性函数,并可以在运行中更新进度 | Qt提供了不同的解决方案:
|
一次性 | 在另一个线程中执行一个已存在的函数,并获取函数返回值 | 使用QtConcurrent:: Run()运行函数。让QFutureWatcher在函数返回时发出finished()信号,并调用QFutureWatcher::result()来获取函数的返回值。 |
一次性 | 使用所有可用的内核,对容器的所有项执行操作。例如,从图像列表中生成缩略图。 | 使用QtConcurrent的QtConcurrent::filter()函数选择容器里的元素,使用QtConcurrent::map()函数对容器里每个元素执行一个操作。如果需要将所有输出打包成一个结构,那可以使用QtConcurrent::filteredReduced()和QtConcurrent:: mappedrereduced()来代替前面各对应的函数。 |
一次性/永久性 | 在一个纯QML应用中执行一个长计算,并且在取到结果后更新GUI | 将计算代码放在.js脚本中,并将其附加到WorkerScript实例中。调用WorkerScript.sendMessage()在一个新的线程中启动计算。让 js脚本也调用sendMessage(),将结果传递回GUI线程。在onMessage中处理结果并更新那里的GUI。 |
永久性 | 有一个对象存活在另一个线程,可以执行不同的任务请求和/或可以接收新的数据工作。 | 子类化QObject来创建一个worker。实例化这个worker对象和一个QThread。将worker移动到新线程。通过队列化的信号和槽连接,向worker对象发送命令或数据。 |
永久性 | 在另一个线程中重复执行一个开销大的操作,该线程不需要接收任何信号或事件。 | 直接在QThread::run()的重新实现中编写无限循环。在没有事件循环的情况下启动线程。让线程发出信号将数据发送回GUI线程。 |
2021年2月7日星期日 下午6:15:59