#(73):Qt 線程相關類
希望上一章有關事件循環的內容還沒有把你繞暈。本章將重新回到有關線程的相關內容上面來。在前面的章節我們了解了有關`QThread`類的簡單使用。不過,Qt 提供的有關線程的類可不那么簡單,否則的話我們也沒必要再三強調使用線程一定要萬分小心,一不留神就會陷入陷阱。
事實上,Qt 對線程的支持可以追溯到2000年9月22日發布的 Qt 2.2。在這個版本中,Qt 引入了`QThread`。不過,當時對線程的支持并不是默認開啟的。Qt 4.0 開始,線程成為所有平臺的默認開啟選項(這意味著如果不需要線程,你可以通過編譯選項關閉它,不過這不是我們現在的重點)。現在版本的 Qt 引入了很多類來支持線程,下面我們將開始逐一了解它們。
`QThread`是我們將要詳細介紹的第一個類。它也是 Qt 線程類中最核心的底層類。由于 Qt 的跨平臺特性,`QThread`要隱藏掉所有平臺相關的代碼。
正如前面所說,要使用`QThread`開始一個線程,我們可以創建它的一個子類,然后覆蓋其`QThread::run()`函數:
~~~
class Thread : public QThread
{
protected:
void run()
{
/* 線程的相關代碼 */
}
};
~~~
然后我們這樣使用新建的類來開始一個新的線程:
~~~
Thread *thread = new Thread;
thread->start(); // 使用 start() 開始新的線程
~~~
注意,從 Qt 4.4 開始,`QThread`就已經不是抽象類了。`QThread::run()`不再是純虛函數,而是有了一個默認的實現。這個默認實現其實是簡單地調用了`QThread::exec()`函數,而這個函數,按照我們前面所說的,其實是開始了一個事件循環(有關這種實現的進一步闡述,我們將在后面的章節詳細介紹)。
`QRunnable`是我們要介紹的第二個類。這是一個輕量級的抽象類,用于開始一個另外線程的任務。這種任務是運行過后就丟棄的。由于這個類是抽象類,我們需要繼承`QRunnable`,然后重寫其純虛函數`QRunnable::run()`:
~~~
class Task : public QRunnable
{
public:
void run()
{
/* 線程的相關代碼 */
}
};
~~~
要真正執行一個`QRunnable`對象,我們需要使用`QThreadPool`類。顧名思義,這個類用于管理一個線程池。通過調用`QThreadPool::start(runnable)`函數,我們將一個`QRunnable`對象放入`QThreadPool`的執行隊列。一旦有線程可用,線程池將會選擇一個`QRunnable`對象,然后在那個線程開始執行。所有 Qt 應用程序都有一個全局線程池,我們可以使用`QThreadPool::globalInstance()`獲得這個全局線程池;與此同時,我們也可以自己創建私有的線程池,并進行手動管理。
需要注意的是,`QRunnable`不是一個`QObject`,因此也就沒有內建的與其它組件交互的機制。為了與其它組件進行交互,你必須自己編寫低級線程原語,例如使用 mutex 守護來獲取結果等。
`QtConcurrent`是我們要介紹的最后一個對象。這是一個高級 API,構建于`QThreadPool`之上,用于處理大多數通用的并行計算模式:map、reduce 以及 filter。它還提供了`QtConcurrent::run()`函數,用于在另外的線程運行一個函數。注意,`QtConcurrent`是一個命名空間而不是一個類,因此其中的所有函數都是命名空間內的全局函數。
不同于`QThread`和`QRunnable`,`QtConcurrent`不要求我們使用低級同步原語:所有的`QtConcurrent`都返回一個`QFuture`對象。這個對象可以用來查詢當前的運算狀態(也就是任務的進度),可以用來暫停/回復/取消任務,當然也可以用來獲得運算結果。注意,并不是所有的`QFuture`對象都支持暫停或取消的操作。比如,由`QtConcurrent::run()`返回的`QFuture`對象不能取消,但是由`QtConcurrent::mappedReduced()`返回的是可以的。`QFutureWatcher`類則用來監視`QFuture`的進度,我們可以用信號槽與`QFutureWatcher`進行交互(注意,`QFuture`也沒有繼承`QObject`)。
下面我們可以對比一下上面介紹過的三種類:
| 特性 | `QThread` | `QRunnable` | `QtConcurrent` |
| -- || -- || -- || -- |
| 高級 API | ? | ? |  |
| 面向任務 | ? |  |  |
| 內建對暫停/恢復/取消的支持 | ? | ? |  |
| 具有優先級 |  | ? | ? |
| 可運行事件循環 |  | ? | ? |
- (1)序
- (2)Qt 簡介
- (3)Hello, world!
- (4)信號槽
- (5)自定義信號槽
- (6)Qt 模塊簡介
- (7)MainWindow 簡介
- (8)添加動作
- (9)資源文件
- (10)對象模型
- (11)布局管理器
- (12)菜單欄、工具欄和狀態欄
- (13)對話框簡介
- (14)對話框數據傳遞
- (15)標準對話框 QMessageBox
- (16)深入 Qt5 信號槽新語法
- (17)文件對話框
- (18)事件
- (19)事件的接受與忽略
- (21)事件過濾器
- (22)事件總結
- (23)自定義事件
- (24)Qt 繪制系統簡介
- (25)畫刷和畫筆
- (26)反走樣
- (27)漸變
- (28)坐標系統
- (29)繪制設備
- (30)Graphics View Framework
- (31)貪吃蛇游戲(1)
- (32)貪吃蛇游戲(2)
- (33)貪吃蛇游戲(3)
- (34)貪吃蛇游戲(4)
- (35)文件
- (36)二進制文件讀寫
- (37)文本文件讀寫
- (38)存儲容器
- (39)遍歷容器
- (40)隱式數據共享
- (41)model/view 架構
- (42)QListWidget、QTreeWidget 和 QTableWidget
- (43)QStringListModel
- (44)QFileSystemModel
- (45)模型
- (46)視圖和委托
- (47)視圖選擇
- (48)QSortFilterProxyModel
- (49)自定義只讀模型
- (50)自定義可編輯模型
- (51)布爾表達式樹模型
- (52)使用拖放
- (53)自定義拖放數據
- (54)剪貼板
- (55)數據庫操作
- (56)使用模型操作數據庫
- (57)可視化顯示數據庫數據
- (58)編輯數據庫外鍵
- (59)使用流處理 XML
- (60)使用 DOM 處理 XML
- (61)使用 SAX 處理 XML
- (62)保存 XML
- (63)使用 QJson 處理 JSON
- (64)使用 QJsonDocument 處理 JSON
- (65)訪問網絡(1)
- (66)訪問網絡(2)
- (67)訪問網絡(3)
- (68)訪問網絡(4)
- (69)進程
- (70)進程間通信
- (71)線程簡介
- (72)線程和事件循環
- (73)Qt 線程相關類
- (74)線程和 QObject
- (75)線程總結
- (76)QML 和 QtQuick 2
- (77)QML 語法
- (78)QML 基本元素
- (79)QML 組件
- (80)定位器
- (81)元素布局
- (82)輸入元素
- (83)Qt Quick Controls
- (84)Repeater
- (85)動態視圖
- (86)視圖代理
- (87)模型-視圖高級技術
- (88)Canvas
- (89)Canvas(續)