線程池 QThreadPool

線程池 QThreadPool

創建線程需要向系統申請資源,線程切換時操作系統會切換線程上下文,可能會從用戶態切換到內核態,當有很多線程時,頻繁地切換線程會導致消耗大量的 CPU 以及內核資源,真正用於計算的資源就減少了,反而會降低程序的效率。線程並不是越多越好,線程池的作用是管理、複用、回收一組線程,控制線程的數量,避免頻繁的創建和銷燬線程而浪費資源。

Qt 中的線程池類爲 QThreadPool,每一個 Qt 程序都有一個全局的線程池,調用 QThreadPool::globalInstance() 得到,它默認最多創建 8 個線程,如果想改變最大線程數則調用 setMaxThreadCount() 進行修改,調用 activeThreadCount() 查看線程池中當前活躍的線程數。

使用線程池挺簡單的,定一個任務類例如叫 Task,繼承 QRunnable 並實現虛函數 run(),Task 的對象作爲 QThreadPool::start() 的參數就可以了,線程池會自動的在線程中調用 Task 的 run() 函數,異步執行。線程池中的 QRunnable 對象太多時並不會爲立即爲每一個 QRunnable 對象創建一個線程,而是讓它們排隊執行,同時最多有 maxThreadCount() 個線程並行執行。

提交給線程池的 QRunnable 對象在它的 run() 函數執行完後會被自動 delete 掉,如果不想線程池刪除它,在調用線程池的 start() 前調用 setAutoDelete(false) 即可。

爲了演示線程池的使用,下面定義一個任務類 Task,屬性 id 爲了便於瞭解任務對象的創建、執行、銷燬,run() 模擬耗時操作,隨機執行 [500, 2500] 毫秒,然後在 main() 函數中創建 100 個 Task 對象提交給線程池,從輸出中可以看到同時只有 8 個任務在執行,run() 執行結束後任務對象被 delete 掉了。

// 文件名: Task.h
#ifndef TASK_H
#define TASK_H

#include <QRunnable>

class Task : public QRunnable {
public:
    Task(int id);
    ~Task();

    void run() Q_DECL_OVERRIDE;

private:
    int id; // 線程的 ID
};

#endif // TASK_H
// 文件名: Task.cpp
#include "Task.h"
#include <QDebug>
#include <QThread>
#include <QDateTime>

Task::Task(int id) : id(id) {

}

Task::~Task() {
    qDebug().noquote() << QString("~Task() with ID %1").arg(id); // 方便查看對象是否被 delete
}
void Task::run() {
    qDebug().noquote() << QString("Start thread %1 at %2").arg(id).arg(QDateTime::currentDateTime().toString("mm:ss.z"));
    QThread::msleep(500 + qrand() % 2000); // 每個 run() 函數隨機執行 [55, 2500] 毫秒,模擬耗時任務
    qDebug().noquote() << QString("End   thread %1 at %2").arg(id).arg(QDateTime::currentDateTime().toString("mm:ss.z"));
}
// 文件名: main.cpp
#include <QApplication>
#include <QThreadPool>
#include "Task.h"

int main(int argc, char *argv[]) {
    QApplication a(argc, argv);

    for (int i = 1; i <= 100; ++i) {
        Task *task = new Task(i); // 創建任務
        QThreadPool::globalInstance()->start(task); // 提交任務給線程池,在線程池中執行
    }

    return a.exec();
}

控制檯輸出如下:

Start thread 1 at 27:05.541
Start thread 6 at 27:05.541
Start thread 5 at 27:05.541
Start thread 2 at 27:05.541
Start thread 3 at 27:05.541
Start thread 8 at 27:05.541
Start thread 7 at 27:05.541
Start thread 4 at 27:05.541
End thread 6 at 27:06.854
~Task() with ID 6
End thread 1 at 27:06.854
~Task() with ID 1
End thread 8 at 27:06.854
~Task() with ID 8
End thread 3 at 27:06.854
~Task() with ID 3
End thread 7 at 27:06.854
End thread 4 at 27:06.854
End thread 2 at 27:06.854
~Task() with ID 7
End thread 5 at 27:06.854
~Task() with ID 4
~Task() with ID 2
~Task() with ID 5
Start thread 9 at 27:06.855
Start thread 10 at 27:06.855
Start thread 11 at 27:06.855
Start thread 12 at 27:06.855
Start thread 13 at 27:06.855
繼承 QThread 和使用 QThreadPool 都能進行多線程編程,那麼什麼時候使用線程池,什麼時候繼承 QThread 創建線程呢?

頻繁創建、耗時短的任務使用線程池來執行更合適,例如通過串口不停地接收到數據,然後交給線程池進行計算處理,如果每接收到一次數據都創建一個新線程就太浪費資源了。耗時長的任務就一般會繼承 QThread 使用多線程,例如使用 QProcess 啓動一個命令行,和命令行進行交互時就可以這麼做。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章