Qt之QThreadPool和QRunnable
簡述
QRunnable 是所有 runnable 對象的基類,而 QThreadPool 類用于管理 QThreads 集合。
QRunnable 類是一個接口,用于表示一個任務或要執行的代碼,需要重新實現 run() 函數。
QThreadPool 管理和循環使用單獨的 QThread 對象,以幫助程序減少創建線程的成本。每個 Qt 應用程序都有一個全局 QThreadPool 對象,可以通過調用 globalInstance() 訪問。
詳細描述
QThreadPool 支持多次執行相同的 QRunnable,通過調用 QThreadPool::tryStart(this) 從 run() 函數內。如果啟用了 autoDelete,當最后一個線程退出 run() 函數,QRunnable 將被刪除。多次調用 QThreadPool::start() 使用相同的 QRunnable,當啟用 autoDelete 時會創建一個競爭條件,不推薦使用。
一定時間未使用線程將會到期,默認到期超時是 30000 毫秒(30秒)。可以使用 setExpiryTimeout() 來改變,設定一個負值,則會禁用到期機制。
調用 maxThreadCount() 查詢使用線程的最大數量,如果需要,可以使用 setMaxThreadCount() 進行更改。默認情況下,maxThreadCount() 是 QThread::idealThreadCount()。activeThreadCount() 函數返回當前正在工作線程的數量。
注意: QThread::idealThreadCount() 提供了計算程序運行所在平臺上支持的輔助線程的最佳數量 - 考慮到操作系統、處理器的數量和機器擁有的處理核的數量。對于只有一個處理器、一個處理核的機器,該函數或許會返回 1。對于擁有多個處理器和處理核的機器,返回值則會相應增大,這個數字不一定會與需要處理的文件個數完全匹配,因此需要將任務劃分,這樣的話,每個輔助線程(假設使用的輔助線程大于 1)都能得到一個和需要處理的文件數量相等的數值(當然,用文件數量目來劃分任務或許不是在所有情況下都是最好的方法,例如:在一個數量為 20 的文件列表中,前 10 個文件很大,后 10 個 文件很小)。
reserveThread() 函數儲備一個線程用于外部使用。當線程完成后,使用 releaseThread(),以便它可以被重新使用。從本質上講,這些函數暫時增加或減少活躍線程的數量,并且當實現耗時的操作時對 QThreadPool 是不可見的,這比較有用。
注意: QThreadPool 是一個管理線程的低級類,高級替代品可以用 Qt Concurrent 模塊。
基本使用
要使用 QThreadPool 的一個線程,子類化 QRunnable 并實現 run() 虛函數。然后創建一個對象,并把它傳遞給 QThreadPool::start() - 這會把可運行對象的擁有權賦給 Qt 的全局線程池,并可以讓它開始運行。
class HelloWorldTask : public QRunnable {void run() {qDebug() << "Hello world from thread " << QThread::currentThread();} }HelloWorldTask *hello = new HelloWorldTask();// QThreadPool取得所有權,并自動刪除 hello QThreadPool::globalInstance()->start(hello);默認情況下,當可運行對象結束時,線程池會自動將其刪除,這也正是我們想要的效果。在某些情況下,如果必須由我們自己負責刪除可運行的對象時,可以通過調用 QRunnable::setAutoDelete(false) 來阻止自動刪除的發生。
自定義信號/槽
打開 QRunnable 所在頭文件,會發現它并不繼承自 QObject,也就是說,根本無法使用 QObject 的特性,例如:信號/槽、事件等。
為了便于使用,我們可以繼承 QObject:
class HelloWorldTask : public QObject, public QRunnable {Q_OBJECT// 自定義信號 signals:void finished();public:void run() {qDebug() << "Hello Thread : " << QThread::currentThreadId();emit finished();} };使用時,連接信號槽即可:
class MainWindow : public QMainWindow {Q_OBJECTpublic:explicit MainWindow(QWidget *parent = 0): QMainWindow(parent){qDebug() << "Main Thread : " << QThread::currentThreadId();// ...HelloWorldTask *hello = new HelloWorldTask();connect(hello, SIGNAL(finished()), this, SLOT(onFinished()));QThreadPool::globalInstance()->start(hello);// ...}protected:void closeEvent(QCloseEvent *event) {if (QThreadPool::globalInstance()->activeThreadCount())QThreadPool::globalInstance()->waitForDone();event->accept();}private slots:void onFinished() {qDebug() << "SLOT Thread : " << QThread::currentThreadId();} };為了獲得安全清楚,對于多線程應用程序來說,最好在終止程序之前,停止所有輔助線程。我們已經通過 closeEvent() 做到了這一點,可以確保在允許終止動作之前讓任何活動的線程先結束掉。
順便再介紹下所屬線程,使用 qDebug 將各自的線程 ID 進行調試輸出。結果如下:
Main Thread : 0xb308
Hello Thread : 0xb33c
SLOT Thread : 0xb308
顯然,槽函數所在線程與主線程相同。
如果想要槽函數在次線程中執行,只需改變信號槽的連接方式:
connect(hello, SIGNAL(finished()), this, SLOT(onFinished()), Qt::DirectConnection);這時,就得到了想要的結果啦:
Main Thread : 0xb030
Hello Thread : 0xacf8
SLOT Thread : 0xacf8
事件的傳遞、數據的交互方式很多,這里只介紹了信號槽。當然還可以使用自定義事件,然后通過 調用 QApplication::sendEvent() 或 QApplication::postEvent() 發送;或者使用 QMetaObject::invokeMethod() 方式均可,這里就不再贅述了。
總結
以上是生活随笔為你收集整理的Qt之QThreadPool和QRunnable的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Dictionary To Dynami
- 下一篇: 动态规划 BZOJ1584 [Usaco