Qt/C++中的异步编程
- 1 介绍
- 2 含义
- 2.1 QtConcurrent
- 2.2 std::future
- 2.3 Qml中的Promise
- 3 使用场景
- 4 代码示例
- 5 注意事项
- 5.1异常处理
- 5.2 线程安全
- 5.3 性能优化
- 5.4 线程间通信
- 5.5 避免死锁
1 介绍
异步编程是现代应用程序开发中不可或缺的一部分。它允许程序在执行耗时任务时保持响应性,特别是在GUI应用程序中。在Qt/C++中,有多种方式来实现异步编程。本文将重点介绍如何使用Qt中的QtConcurrent库,Qml中ES6标准的Promise还有C++标准库std::future进行异步编程,并讲解他们之间的区别。
2 含义
2.1 QtConcurrent
QtConcurrent 命名空间提供了高级 API,使编写多线程程序成为可能,而无需使用诸如互斥、读写锁、等待条件或 semaphores 等低级线程原语。使用 QtConcurrent 编写的程序会根据可用处理器内核的数量自动调整所使用的线程数。这意味着现在编写的应用程序将来在多核系统上部署时仍可继续扩展。
- Concurrent Map and Map-Reduce
QtConcurrent 包含用于并行列表处理的函数式编程 API,包括用于共享内存(非分布式)系统的 MapReduce 和 FilterReduce 实现,以及用于管理 GUI 应用程序中异步计算的类:QtConcurrent::map()对容器中的每个项目应用一个函数,对项目进行就地修改。QtConcurrent::mapped()类似map(),不同之处在于它返回一个带有修改的新容器。QtConcurrent::mappedReduced()类似于mapped(),只不过修改后的结果被缩小或折叠成一个结果。
- Concurrent Filter and Filter-Reduce
QtConcurrent::filter()根据过滤函数的结果从容器中移除所有项目。QtConcurrent::filtered()类似filter(),但它返回一个包含过滤结果的新容器。QtConcurrent::filteredReduced()类似filtered(),只是过滤后的结果被缩小或折叠成一个结果。- Concurrent Run (常用)
QtConcurrent::run()在另一个线程中运行一个函数。
- QFuture (常用) 表示异步计算的结果。它类似于标准库中的std::future,但集成了Qt的信号和槽机制,使用起来更加方便。
- QFutureWatcher(常用) 允许使用信号和插槽监控
QFuture。 QFutureIterator允许遍历通过QFuture获得的结果。QFutureSynchronizer是一个方便的类,可自动同步多个QFutures。
- QFutureWatcher(常用) 允许使用信号和插槽监控
2.2 std::future
std::future对象用来访问异步操作的结果。具体细节可以参考我之前的文章《C++库std::future》。std::promise对象用来设置异步操作的结果,并将其传递给关联的std::future对象。简而言之,std::promise和std::future配合使用,std::promise用于设置结果,std::future用于获取结果。
2.3 Qml中的Promise
Qml中使用的是JavaScript,Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。参考<<Promise含义>>
3 使用场景
| 需求 | 设计 |
|---|---|
| 文件读取 | 一个线程负责读取文件内容,完成后通过std::promise/QtConcurrent/Promise通知主线程文件内容已准备好。 |
| 压缩解压 | 一个线程负责压缩或者解压文件,完成后通过std::promise/QtConcurrent/Promise通知主线程操作结果。 |
| 网络请求 | 在进行网络请求时,可以使用std::promise/QtConcurrent/Promise在请求完成时通知结果,无论成功还是失败。 |
| 数据库查询 | 异步执行数据库查询,查询完成后通过std::promise/QtConcurrent/Promise传递查询结果给主线程。 |
| 图像处理 | 在另一个线程中处理图像(如缩放、滤镜应用),处理完成后通过promise告知处理结果。 |
| 复杂计算 | 将复杂的计算任务分配给工作线程,计算结束后通过std::promise/QtConcurrent/Promise通知计算结果。 |
| 定时任务 | 设置一个定时器,在特定时间点通过std::promise/QtConcurrent/Promise触发某个事件或通知。 |
| 视频转码 | 视频转码是一个耗时任务,可以在转码完成时使用std::promise/QtConcurrent/Promise通知转换状态和输出路径。 |
| 并行算法 | 在并行算法中,每个线程完成一部分计算后,可以使用std::promise/QtConcurrent/Promise汇总结果到主线程。 |
| 事件驱动系统 | 在事件驱动的架构中,std::promise/QtConcurrent/Promise可用于异步事件的响应,例如事件处理完成的通知 |
| 资源加载 | 游戏或应用中的资源加载(如纹理、音频)可以在后台线程进行,加载完毕后通过std::promise/QtConcurrent/Promise通知加载成功 |
| 缓存更新 | 异步更新缓存内容,更新完成后通知其他等待该资源的线程 |
| 消息队列处理 | 在处理消息队列时,每个消息的处理结果可以通过std::promise/QtConcurrent/Promise传递给等待的线程 |
| 异步日志记录 | 日志写入操作可以异步执行,日志写入完成时通过std::promise/QtConcurrent/Promise通知日志系统 |
| 数据分析 | 在进行大数据分析时,可以使用std::promise/QtConcurrent/Promise在分析任务完成后通知结果,以便进一步处理或展示 |
| 硬件交互 | 与硬件设备的交互(如传感器读取、设备控制)可以是异步的,通过std::promise/QtConcurrent/Promise通知操作状态或读取结果。 |
4 代码示例
我们以一个压缩文件的场景为例,展示如何在Qt/C++中使用异步编程。
使用QtConcurrent进行文件压缩:
#include <QtConcurrent>
#include <QDebug>