Qt中的多线程技术
Qt提供了许多类和函数来处理线程。以下是从Qt程序员可用于实现多线程应用程序的四种不同方法。
QThread:带有可选事件循环的低级API
QThread是Qt中所有线程控制的基础。每个QThread实例代表和控制一个线程。
QThread可以通过直接实例化或通过子类化来创建。实例化一个QThread提供了一个并行的事件循环,允许QObject槽在辅线程中调用。通过子类化QThread,应用程序可以在启动事件循环之前初始化新线程,或者在无需事件循环的情况下运行并行代码。
请参见QThread类参考以及线程示例,了解如何使用QThread。
QThreadPool和QRunnable:重用线程
频繁地创建和销毁线程可能成本高昂。为了减少这种开销,可以用现存的线程重用新的任务。QThreadPool是一组可重用的QThread。
要在QThreadPool的线程中运行代码,重写QRunnable::run()并实例化可子类化的QRunnable。使用QThreadPool::start()将QRunnable放入QThreadPool的运行队列。当有可用的线程时,QRunnable::run()中的代码将在该线程中执行。
每个Qt应用程序都有一个全局线程池,该线程池可通过QThreadPool::globalInstance()访问。这个全局线程池会根据CPU核心的数量自动维护最佳线程数量。然而,可以创建并显式管理一个单独的QThreadPool。
Qt并发:使用高级API
Qt Concurrent模块提供了处理一些常见的并行计算模式的高级函数:映射、过滤和归约。与使用QThread和QRunnable不同,这些函数无需使用低级线程原语,例如互斥锁或信号量。相反,它们返回一个QFuture对象,用于在函数准备好后检索其结果。QFuture还可以用于查询计算进度和暂停/恢复/取消计算。为了方便,QFutureWatcher通过信号和槽允许与QFuture交互。
Qt Concurrent的映射、过滤和归约算法会自动将计算分配到所有可用的处理器核心,所以今天编写的应用程序在未来部署到具有更多核心的系统时仍能继续扩展。
此模块还提供了QtConcurrent::run()函数,它可以在另一个线程中运行任何函数。但是,QtConcurrent::run()只支持映射、过滤和归约函数可用功能子集。QFuture可用于检索函数的返回值和检查线程是否正在运行。但是,QtConcurrent::run()函数只使用一个线程,不能暂停/恢复/取消,也无法查询进度。
有关各个函数的详细信息,请参阅Qt Concurrent模块文档。
WorkerScript: QML中的多线程
QML类型的WorkerScript允许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 |
可以指定线程优先级 | 是 | 是 | |||
线程可以运行事件循环 | 是 | ||||
线程可以通过信号接收数据更新 | 是(由工作QObject接收) | 是(由WorkerScript接收) | |||
线程可以通过信号进行控制 | 是(由QThread接收) | 是(由QFutureWatcher接收) | |||
可以通過QFuture監視線程 | 部分 | 是 | |||
內置暫停/恢復/取消功能 | 是 |
用例示例
線程的生命周期 | 操作 | 解决方案 |
---|---|---|
一次调用 | 在另一個線程中運行新的線性函數,運行期間可选地提供進度更新。 | Qt提供建立不同的解決方案
|
一次调用 | 在另一個線程中運行現有的函數並獲得其返回值。 | 使用QtConcurrent::run運行函數。當函數返回時,讓QFutureWatcher發射finished()信號,並調用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線程。 |
© 2024 The Qt Company Ltd. 本文件中包含的文檔貢獻是各自所有人的版權。此文檔提供受GNU自由文檔許可證版本1.3的條件許可,該許可證由自由軟件基金會發布。Qt及其相關標誌是芬蘭及其他國家的The Qt Company Ltd.的商標。所有其他商標屬於其各自的所有者。