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

關(guān)于多線程編程教程及經(jīng)典應(yīng)用案例的匯總分析
多線程好還是單線程好?單線程和多線程的區(qū)別 優(yōu)缺點(diǎn)分析
什么是多線程編程?多線程編程基礎(chǔ)知識(shí)
關(guān)于Linux下多線程編程技術(shù)學(xué)習(xí)總結(jié)

RT-Thread學(xué)習(xí)筆記 --(6)RT-Thread線程間通信學(xué)習(xí)過程總結(jié)

基于QT自制上位機(jī)(多線程)

關(guān)于Python多進(jìn)程和多線程詳解

評(píng)論