文章目錄
Qt線程基礎
Qt 提供平臺獨立的線程類實現對線程編程的支持,並且以線程安全的方式實現事件委派(posting events)和基於信號槽的跨線程連接。 這使得它易於開發便攜式多線程 Qt 應用程序和利用多處理器機器的優勢。 具有多個線程的程序分配給多個核心,從而以真正的併發方式實現。因此,將工作分配到多個線程可以使程序在多核CPU上運行得更快,因爲可以使用其他內核。
何時使用多線程?
線程基本上有兩種用例:
- 通過使用多核處理器加快處理速度。
- 一個程序,既要執行費時的操作,又不凍結用戶界面。
GUI線程和工作線程
每個程序在啓動時都有一個線程。該線程稱爲“主線程”(在Qt應用程序中也稱爲“GUI線程”)。Qt GUI必須在主線程中運行。所有小部件和幾個相關的類(例如QPixmap)在輔助線程中不起作用。輔助線程通常被稱爲“工作線程”,因爲它用於從主線程卸載處理工作。
同時訪問數據
每個線程都有自己的堆棧,這意味着每個線程都有自己的調用歷史和局部變量。與進程不同,線程共享相同的地址空間。下圖顯示了線程的構建塊如何位於內存中。程序計數器和非活動線程的寄存器通常保存在內核空間中。每個線程都有一個代碼的共享副本和一個單獨的堆棧。
如果兩個線程具有指向同一對象的指針,則兩個線程可能同時訪問該對象,這可能會破壞對象的完整性。
有時需要從不同的線程訪問一個對象;例如,當存在於不同線程中的對象需要通信時。由於線程使用相同的地址空間,因此線程交換數據比處理更容易,更快。數據不必序列化和複製。傳遞指針是可能的,但必須嚴格協調哪個線程觸及哪個對象。必須防止在一個對象上同時執行操作。
在線程中創建的所有對象都可以在該線程內安全使用,前提是其他線程沒有對它們的引用,並且對象沒有與其他線程的隱式耦合。
當靜態成員,單例或全局數據在實例之間共享數據時,可能發生這種隱式耦合。
選擇哪種QT多線程技術?
1.QThread:具有可選事件循環的低級API
QThread是Qt中所有線程控制的基礎。每個QThread實例代表並控制一個線程。
QThread既可以直接實例化,也可以子類化。實例化QThread提供了並行事件循環,允許在輔助線程中調用QObject槽。對QThread進行子類化允許應用程序在啓動其事件循環之前初始化新線程,或者在沒有事件循環的情況下運行並行代碼。
有關如何使用QThread的演示,請參閱QThread類參考和線程示例。
http://doc.qt.io/archives/qt-5.8/qthread.html
2.QThreadPool和QRunnable:重用線程
經常創建和銷燬線程可能很昂貴。爲了減少這種開銷,可以將現有線程重用於新任務。QThreadPool是可重複使用的QThreads的集合。
要在QThreadPool的一個線程中運行代碼,重新實現QRunnable :: run()並實例化子類QRunnable。使用QThreadPool :: start()將QRunnable放入QThreadPool的運行隊列中。當一個線程可用時,QRunnable :: run()中的代碼將在該線程中執行。
每個Qt應用程序都有一個全局線程池,可以通過QThreadPool :: globalInstance()訪問。此全局線程池根據CPU中的核心數自動維護最佳線程數。但是,可以顯式創建和管理單獨的QThreadPool。
http://doc.qt.io/archives/qt-5.8/qthreadpool.html
3.Qt Concurrent:使用高級API
在Qt Concurrent(並行)模塊提供了一些常見的並行計算模式處理的高級別功能:map, filter, and reduce。與使用QThread和QRunnable不同,這些函數從不需要使用低級線程原語,如互斥鎖或信號量。相反,它們返回一個QFuture對象,可用於在函數準備就緒時檢索函數的結果。QFuture還可用於查詢計算進度和暫停/恢復/取消計算。爲了方便起見,QFutureWatcher使得能夠與QFuture經由信號和槽交互。
Qt Concurrent的map,filter和reduce算法自動在所有可用處理器內核之間分配計算,因此今天編寫的應用程序將在以後在具有更多內核的系統上部署時繼續擴展。
該模塊還提供了QtConcurrent :: run()函數,該函數可以在另一個線程中運行任何函數。但是,QtConcurrent :: run()僅支持map,filter和reduce函數可用的一部分功能。該QFuture可用於獲取函數的返回值和檢查。但是,對QtConcurrent :: run()的調用僅使用一個線程,無法暫停/恢復/取消,並且無法查詢進度。
有關各個功能的詳細信息,請參閱Qt Concurrent模塊文檔。
http://doc.qt.io/archives/qt-5.8/qtconcurrent-index.html
QT多線程技術對比
QThread | QRunnable and QThreadPool | QtConcurrent::run() | Qt Concurrent (Map, Filter, Reduce) | WorkerScript | |
---|---|---|---|---|---|
語言 | C++ | C++ | C++ | C++ | QML |
可以指定線程優先級 | √ | √ | |||
線程可以運行事件循環 | √ | ||||
線程可以通過信號接收數據更新 | √ (received by a worker QObject) | √ (由WorkerScript收到) | |||
可以使用信號控制線程 | √ (由QThread收到) | √ (由QFutureWatcher收到) | |||
可以通過QFuture監控線程 | 部分 | √ | |||
內置暫停/恢復/取消功能 | √ |
使用場景示例
線程的生命週期 | 操作 | 解決方案 |
---|---|---|
一次調用 | 在另一個線程中運行新的線性函數,可選擇在運行期間進行進度更新。 | Qt提供不同的解決方案:①將函數放在QThread :: run()的重新實現中並啓動QThread。發出信號以更新進度。②重新實現QRunnable ::run(),並添加QRunnable到QThreadPool。寫入線程安全變量以更新進度。③使用QtConcurrent :: run()運行該函數。寫入線程安全變量以更新進度。 |
一次調用 | 在另一個線程中運行現有函數並獲取其返回值。 | 使用QtConcurrent :: run()運行該函數。有QFutureWatcher發射finished()信號時,該功能已恢復,並調用QFutureWatcher ::result()來獲取函數的返回值。 |
一次調用 | 使用所有可用內核對容器的所有項執行操作。例如,從圖像列表中生成縮略圖。 | 使用Qt Concurrent的QtConcurrent :: filter()函數選擇容器元素,使用QtConcurrent :: map()函數將操作應用於每個元素。要將輸出摺疊爲單個結果,請改用QtConcurrent :: filteredReduced()和QtConcurrent :: mappedReduced()。 |
一次調用/常駐 | 在純QML應用程序中進行長時間計算,並在結果準備好後更新GUI。 | 將計算代碼放在.js腳本中並將其附加到WorkerScript實例。調用sendMessage()以在新線程中啓動計算。讓腳本也調用WorkerScript :: sendMessage(),將結果傳遞迴GUI線程。處理結果onMessage並在那裏更新GUI。 |
常駐 | 線程中存在一個對象,可以根據請求執行不同的任務和/或可以接收要處理的新數據。 | 子類化QObject以創建worker。實例化此worker對象和QThread。將worker移動到新線程。通過排隊的信號槽連接向worker對象發送命令或數據。 |
常駐 | 在另一個線程中重複執行昂貴的操作,其中線程不需要接收任何信號或事件。 | 在QThread :: run()的重新實現中直接編寫無限循環。在沒有事件循環的情況下啓動線程。讓線程發出信號以將數據發送回GUI線程。 |
QT中的線程類
Qt以獨立於平臺的線程類的形式提供線程支持,一種發佈事件的線程安全方式,以及跨線程的信號槽連接。這使得開發便攜式多線程Qt應用程序和利用多處理器機器變得容易。多線程編程也是一種有用的範例,用於在不凍結應用程序的用戶界面的情況下執行耗時的操作。
線程類 | 說明 |
---|---|
QtConcurrentRun | 提供了一種在一個單獨的線程運行函數的方法。 |
QtConcurrentFilter | 提供並行過濾器和 Filter-Reduce。 |
QtConcurrentMap | 提供並行 Map 和 MapReduce。 |
QAtomicInt | 提供整數平臺無關原子操作 |
QAtomicPointer | 模板類,它提供了指針平臺無關的原子操作 |
QFuture | 代表了一個異步計算結果 |
QFutureSynchronizer | 方便類,簡化 QFuture 同步 |
QFutureWatcher | 允許使用信號和插槽來監視 QFuture |
QMutex | 線程之間的訪問序列化 |
QMutexLocker | 方便類,它簡化了鎖定和解鎖互斥 |
QReadLocker | 方便類,它簡化了鎖定和解鎖讀寫鎖進行讀訪問 |
QReadWriteLock | 讀寫鎖 |
QRunnable | 所有 runnable objects 的基類 |
QSemaphore | 一般計數信號量 |
QThread | 平臺無關的線程 |
QThreadPool | 管理 QThreads 集合,就是線程池 |
QThreadStorage | 每個線程的數據存儲 |
QWaitCondition | 條件變量的線程同步 |
QWriteLocker | 方便的類,它簡化了鎖定和解鎖讀寫鎖的寫訪問 |
QtConcurrent | 高層次的 API ,使人們可以編寫不使用低級別的線程原語的多線程程序 |
Reference
Threading Basics:
http://doc.qt.io/archives/qt-5.8/thread-basics.html
Multithreading Technologies in Qt:
http://doc.qt.io/archives/qt-5.8/threads-technologies.html
Reentrancy and Thread-Safety:
http://doc.qt.io/archives/qt-5.10/threads-reentrancy.html