一、導讀
本篇文章是關于Qt多線程應用設計方法的總結,描述了Qt中進行多線程設計的四種方法,并列舉了常見應用場景下的多線程設計方案。合理選擇對應的方法來解決實際開發中遇到的問題有助于對應用程序進行更合理設計。
二、【方法一】 QThread:帶有可選事件循環的底層API
QThread是Qt中所有線程的基礎,每個QThread實例代表和控制一個線程。使用QThread創建線程有兩種方法:
(1)直接實例化創建:提供了一個并行事件循環,允許在輔助線程中調用QObject槽函數。
(2)子類化創建:繼承QThread,允許應用程序在啟動事件循環之前初始化新線程,或者在沒有事件循環的情況下運行并行代碼。
三、【方法二】 QThreadPool和QRunnable:重用線程
在實際開發中,頻繁創建和銷毀線程的代價可能會很高。為了減少這種開銷,可以對新任務重用現有的線程。QThreadPool是可重用QThread的集合。
要在QThreadPool的一個線程中運行代碼,需要重新實現QRunnable::run()并實例子類化的QRunnable。
使用````QThreadPool::start()將QRunnable放到QThreadPool的運行隊列中。當線程可用時,QRunnable::run()```中的代碼將在該線程中執行。
【備注】:每個Qt應用程序都有一個全局線程池,可以通過QThreadPool::globalInstance()訪問這個線程池。這個全局線程池根據CPU中的核心數量會自動維護最佳的線程數量。但是在實際開發中,可以顯式創建和管理一個單獨的QThreadPool。
四、【方法三 】Qt并發:使用高級API
Qt并發模塊提供了許多高級功能,用來處理一些常見的并行計算模式。例如:map、filter和reduce。Qt并發與使用QThread和QRunnable不同,這些函數不需要使用底層的線程原語,如互斥或信號量等。相反,它們返回的是一個QFuture對象,該對象可用于在準備線程或者線程完成時自動檢索函數的結果;QFuture還可以用來查詢、計算進度和暫停/恢復/取消計算。更方便的是,QFutureWatcher允許通過信號和槽函數與QFutures進行交互。
Qt Concurrent的并行計算模型:map、filter和reduce等算法會自動將計算負載分配到所有可用的處理器核心上,因此,我們今天編寫的應用程序,如果在以后部署到擁有更多處理器核心的系統上時將繼續得以擴展和使用,這一點非常方便。
Qt并發模塊還提供了QtConcurrent::run()函數,它可以在另一個線程中運行任何函數。但是,QtConcurrent::run()只支持map、filter和reduce函數可用的特性子集,QFuture可用于檢索函數的返回值并檢查線程是否正在運行。
但是,對QtConcurrent::run()的調用只使用一個線程,不能暫停/恢復/取消,也不能查詢進程。
WorkerScript QML類型允許JavaScript代碼與GUI線程并行運行。每個WorkerScript實例可以附加一個.js腳本。調用WorkerScript.sendMessage()時,腳本將在單獨的線程(和單獨的QML上下文)中運行。當腳本運行完成時,它可以將一個回復發送回GUI線程,該線程將調用WorkerScript.onMessage()信號處理程序。
使用WorkerScript類似于使用已移動到另一個線程的worker QObject,數據通過信號在線程之間進行傳輸。
【注】這種方法在QML中使用
六、如何選擇上述四種多線程設計方案
如上所示,Qt為開發多線程應用程序提供了幾種解決方案。而對于多線程應用程序的解決方案的選擇取決于:新線程的用途和線程的生存期。下面是Qt線程技術的一張比較表:
序號 | 特點 | QThread | QRunnable 和QThreadPool | QtConcurrent::run() | Qt Concurrent(Map/Filter/Reduce) | WorkerScript |
---|---|---|---|---|---|---|
1 | 開發語言 | C++ | C++ | C++ | C++ | QML |
2 | 是否可以指定線程優先級 | 是 | 是 | |||
3 | 線程是否可以運行一個事件循環 | 是 | ||||
4 | 線程是否可以通過信號接收數據更新 | 是(received by a worker QObject) | 是 (received by WorkerScript) | |||
5 | 線程是否可以使用信號來控制 | 是(received by QThread) | 是 (received by QFutureWatcher) | |||
6 | 線程是否可以通過QFuture來監控 | 部分可以 | 是 | |||
7 | 是否擁有內置能力:取消/暫停/恢復 | 是 | ||||
七、Qt多線程應用設計方案
在本小節中,列出了Qt中常見的幾種多線程應用的設計方案,如下表所示:
線程生命周期 | 應用場景 | 解決方案 |
---|---|---|
一次調用 | 在另一個線程中運行一個新的線程函數,可以選擇在運行期間進行進度更新。 | Qt提供了不同的解決方案: 1、 將該函數放在QThread::run()的重新實現中,并啟動QThread,發出信號更新進度。 2、該函數放在QRunnable::run()的重新實現中,并將QRunnable添加到QThreadPool中,寫入線程安全的變量更新進度。 3、使用QtConcurrent:: Run()運行函數,寫入線程安全的變量更新進度。 |
一次調用 | 在另一個線程中運行一個現有函數并獲取它的返回值。 | 使用QtConcurrent:: Run()運行函數,讓QFutureWatcher在函數返回時發出finished()信號,并調用QFutureWatcher::result()來獲取函數的返回值。 |
一次調用 | 使用所有可用的硬件資源對容器(Container)的所有項執行操作。例如:從圖像列表生成縮略圖。 | 使用QtConcurrent的QtConcurrent::filter()函數來選擇容器元素,使用QtConcurrent::map()函數來為每個元素關聯一個操作。 |
一次調用/永久存在 | 在純QML應用程序中完成長時間的計算,并在結果準備好時更新GUI。 | 將計算代碼放在.js腳本中,并將其附加到WorkerScript實例。調用WorkerScript.sendMessage()在新線程中啟動計算。讓腳本也調用sendMessage(),將結果傳遞回GUI線程。在onMessage中處理結果并更新GUI。 |
永久存在 | 在另一個線程中有一個對象,它可以根據請求執行不同的任務,并且可以接收、處理新的數據。 | 子類化一個QObject來創建一個worker,實例化這個worker對象和一個QThread,將worker移動到新線程,通過排隊的信號和槽函數連接向worker對象發送命令或數據。 |
永久存在 | 在另一個線程中重復執行開銷較大的操作,其中該線程不需要接收任何信號或事件。 | 直接在QThread::run()的重新實現中寫入無限循環,在沒有事件循環的情況下啟動線程,讓線程發出信號將數據發送回GUI線程。 |
審核編輯:劉清
-
cpu
+關注
關注
68文章
10899瀏覽量
212609 -
信號處理
+關注
關注
48文章
1040瀏覽量
103358 -
GUI
+關注
關注
3文章
662瀏覽量
39819
原文標題:這四種使用Qt多線程設計的“姿勢”...
文章出處:【微信號:嵌入式小生,微信公眾號:嵌入式小生】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論