希望上一章有關事件循環的內容還沒有把你繞暈。本章將重新回到有關線程的相關內容上面來。在前面的章節我們瞭解了有關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 | ✘ | ✘ | ✔ |
面向任務 | ✘ | ✔ | ✔ |
內建對暫停/恢復/取消的支持 | ✘ | ✘ | ✔ |
具有優先級 | ✔ | ✘ | ✘ |
可運行事件循環 | ✔ | ✘ | ✘ |