Qt中多線程QThread、QThreadPool、QConCurrent三種方式對比分析
第一層:QThread —— 基石與底層控制(管理“線程”)
QThread 是 Qt 多線程的基石,本質(zhì)上是對操作系統(tǒng)原生線程的面向?qū)ο蠓庋b。
- 核心思想:你直接創(chuàng)建和控制線程的生命周期。
- 使用方式
- 繼承重寫法:繼承
QThread重寫run()函數(shù)。簡單粗暴,但不夠靈活,線程和任務(wù)耦合在一起。 - moveToThread法:繼承
QObject,通過object->moveToThread(thread)將對象移入線程。這是 Qt 官方推薦的寫法,利用信號槽驅(qū)動(dòng)任務(wù)執(zhí)行,線程與任務(wù)解耦。
- 繼承重寫法:繼承
- 優(yōu)點(diǎn):控制力最強(qiáng)。你可以精確控制線程的優(yōu)先級、棧大小、啟動(dòng)、停止、甚至是線程內(nèi)的事件循環(huán)。
- 缺點(diǎn):開銷大(線程創(chuàng)建/銷毀系統(tǒng)開銷大);管理難(如果隨手
new QThread,容易導(dǎo)致線程數(shù)量失控);不適合海量短任務(wù)。 - 適用場景:常駐線程。比如后臺一直運(yùn)行的硬件通信輪詢、服務(wù)器監(jiān)聽、需要獨(dú)立事件循環(huán)處理信號槽的長生命周期任務(wù)。
代碼示例:
- QThread (moveToThread 法) —— 面向?qū)ο?,事件循環(huán)驅(qū)動(dòng)
這是 Qt 官方推薦的做法。我們將創(chuàng)建一個(gè) Worker 類,將其移入 QThread,通過信號槽觸發(fā)任務(wù)并返回結(jié)果。
核心特征:線程常駐、支持信號槽、擁有獨(dú)立事件循環(huán)。
#include <QCoreApplication>
#include <QThread>
#include <QObject>
#include <QDebug>
// 1. 定義工作類(千萬不要繼承QThread)
class Worker : public QObject {
Q_OBJECT
public:
explicit Worker(int id) : m_id(id) {}
public slots:
// 槽函數(shù):執(zhí)行耗時(shí)任務(wù)
void doWork() {
qDebug() << "Worker" << m_id << "running in thread:" << QThread::currentThreadId();
QThread::msleep(1000); // 模擬耗時(shí)操作
int result = m_id * 100; // 模擬計(jì)算結(jié)果
emit resultReady(m_id, result); // 發(fā)送結(jié)果信號
}
signals:
void resultReady(int id, int result);
private:
int m_id;
};
int main(int argc, char *argv[]) {
QCoreApplication a(argc, argv);
qDebug() << "Main thread:" << QThread::currentThreadId();
QList<QThread*> threads;
QList<Worker*> workers;
// 創(chuàng)建 5 個(gè)工作對象和 5 個(gè)線程
for (int i = 0; i < 5; ++i) {
Worker *worker = new Worker(i);
QThread *thread = new QThread();
worker->moveToThread(thread); // 將工作對象移入新線程
// 連接啟動(dòng)信號:線程啟動(dòng)后,調(diào)用 worker 的 doWork
QObject::connect(thread, &QThread::started, worker, &Worker::doWork);
// 連接結(jié)果信號:將結(jié)果打印到主線程
QObject::connect(worker, &Worker::resultReady, [](int id, int result){
qDebug() << " -> Got result from Worker" << id << ":" << result;
});
// 連接清理信號:任務(wù)完成后,安全退出線程并清理內(nèi)存
QObject::connect(worker, &Worker::resultReady, thread, &QThread::quit);
QObject::connect(thread, &QThread::finished, worker, &QObject::deleteLater);
QObject::connect(thread, &QThread::finished, thread, &QObject::deleteLater);
threads.append(thread);
workers.append(worker);
}
// 啟動(dòng)所有線程
for (QThread *t : threads) {
t->start();
}
return a.exec();
}代碼運(yùn)行結(jié)果:

第二層:QThreadPool + QRunnable —— 任務(wù)池化(管理“任務(wù)”)
隨著需求的復(fù)雜,你發(fā)現(xiàn)頻繁創(chuàng)建銷毀 QThread 太浪費(fèi)資源,于是進(jìn)階到了線程池。QRunnable 是任務(wù),QThreadPool 是執(zhí)行任務(wù)的池子。
- 核心思想:將“線程”和“任務(wù)”解耦。你只需要寫任務(wù),線程的創(chuàng)建、復(fù)用、銷毀交給線程池。
- 使用方式:繼承
QRunnable重寫run(),然后丟給QThreadPool::start()。 - 進(jìn)階體現(xiàn)
- 資源復(fù)用:避免了頻繁創(chuàng)建/銷毀線程的系統(tǒng)調(diào)用。
- 并發(fā)控制:通過
maxThreadCount()限制最大并發(fā)數(shù),防止系統(tǒng)資源耗盡。
- 缺點(diǎn):
QRunnable本身不是繼承自QObject,不支持信號槽(雖然可以手動(dòng)使用QMetaObject::invokeMethod或組合QObject來解決,但終歸麻煩);缺乏取消、進(jìn)度匯報(bào)等高級功能。 - 適用場景:大量獨(dú)立的、無狀態(tài)的短任務(wù)。比如批量處理圖片、批量下載文件、并發(fā)的數(shù)學(xué)計(jì)算。丟進(jìn)池子,跑完即止。
代碼示例:
- QThreadPool + QRunnable —— 任務(wù)池化,拿來即走
我們將任務(wù)封裝為 QRunnable,丟給全局線程池。因?yàn)?QRunnable 不是 QObject,無法直接使用信號槽,這里我們使用 C++11 的 std::function 回調(diào)來返回結(jié)果。
核心特征:線程復(fù)用、無事件循環(huán)、適合一次性短任務(wù)、需手動(dòng)處理結(jié)果回傳。
#include <QCoreApplication>
#include <QThreadPool>
#include <QRunnable>
#include <QDebug>
// 1. 定義任務(wù)類
class ComputeTask : public QRunnable {
public:
ComputeTask(int id, std::function<void(int, int)> callback)
: m_id(id), m_callback(callback)
{
// 設(shè)置任務(wù)執(zhí)行完后自動(dòng)銷毀,防止內(nèi)存泄漏
setAutoDelete(true);
}
// 重寫 run 函數(shù)
void run() override {
qDebug() << "Task" << m_id << "running in thread:" << QThread::currentThreadId();
QThread::msleep(1000); // 模擬耗時(shí)操作
int result = m_id * 100;
// QRunnable 沒有信號槽,使用回調(diào)函數(shù)將結(jié)果傳回
if (m_callback) {
m_callback(m_id, result);
}
}
private:
int m_id;
std::function<void(int, int)> m_callback;
};
int main(int argc, char *argv[]) {
QCoreApplication a(argc, argv);
qDebug() << "Main thread:" << QThread::currentThreadId();
// 設(shè)置全局線程池最大線程數(shù)為 2(為了演示線程復(fù)用)
QThreadPool::globalInstance()->setMaxThreadCount(2);
for (int i = 0; i < 5; ++i) {
// 定義回調(diào)函數(shù)接收結(jié)果
auto callback = [](int id, int result) {
qDebug() << " -> Got result from Task" << id << ":" << result;
};
ComputeTask *task = new ComputeTask(i, callback);
// 丟給線程池執(zhí)行
QThreadPool::globalInstance()->start(task);
}
// 等待所有任務(wù)完成再退出(僅為了演示,實(shí)際GUI程序不推薦主線程wait)
QThreadPool::globalInstance()->waitForDone();
qDebug() << "All tasks finished.";
return 0; // 不需要 a.exec(),因?yàn)闆]有事件循環(huán)
}代碼運(yùn)行結(jié)果:

第三層:QtConcurrent —— 高層函數(shù)式并發(fā)(管理“結(jié)果”)
當(dāng)你要處理的不僅是任務(wù),而是數(shù)據(jù)集合(比如一個(gè) List 里的所有元素都要做相同操作),QRunnable 寫起來還是很啰嗦(要寫類、要重寫run、要處理數(shù)據(jù)傳遞)。于是進(jìn)階到了 QtConcurrent。
- 核心思想:聲明式編程。你只需要告訴 Qt “對這堆數(shù)據(jù)執(zhí)行什么函數(shù)”,Qt 自動(dòng)幫你分配到線程池(底層默認(rèn)使用全局
QThreadPool)。 - 使用方式:
QtConcurrent::map()/filter()/mappedReduced()/run()。配合 C++11 的 Lambda 表達(dá)式,代碼極其精簡。 - 進(jìn)階體現(xiàn)
- 極簡 API:一行代碼即可實(shí)現(xiàn)多并發(fā)。
- QFuture 綁定:返回
QFuture對象,可以隨時(shí)查詢計(jì)算進(jìn)度(progressValue)、掛起(suspend)、恢復(fù)(resume)、取消(cancel)以及獲取最終結(jié)果。 - 自動(dòng)負(fù)載均衡:根據(jù) CPU 核心數(shù)自動(dòng)切分?jǐn)?shù)據(jù)塊,實(shí)現(xiàn)最佳負(fù)載均衡。
- 缺點(diǎn):靈活性最低。無法控制任務(wù)具體跑在哪個(gè)線程,無法像
QThread那樣運(yùn)行獨(dú)立的事件循環(huán)。 - 適用場景:數(shù)據(jù)并行處理。比如對一個(gè)包含 10000 個(gè)元素的
QList同時(shí)求平方、過濾出符合條件的對象等。
- 代碼示例:
- 我們不再需要顯式定義類,直接使用
QtConcurrent::run將一個(gè) Lambda 表達(dá)式丟進(jìn)線程池,并利用QFutureWatcher異步監(jiān)聽結(jié)果。 - 核心特征:代碼極簡、自動(dòng)數(shù)據(jù)分片、QFuture 提供進(jìn)度/取消/結(jié)果監(jiān)聽、無需關(guān)心線程。
- 我們不再需要顯式定義類,直接使用
#include <QCoreApplication>
#include <QtConcurrent>
#include <QFutureWatcher>
#include <QDebug>
int main(int argc, char *argv[]) {
QCoreApplication a(argc, argv);
qDebug() << "Main thread:" << QThread::currentThreadId();
// 同樣限制全局線程池大小以便觀察
QThreadPool::globalInstance()->setMaxThreadCount(2);
// 使用 QFutureWatcher 監(jiān)聽異步結(jié)果(因?yàn)?QtConcurrent 本身是非阻塞的)
QFutureWatcher<int> *watcher = new QFutureWatcher<int>();
// 當(dāng)單個(gè)結(jié)果就緒時(shí)觸發(fā)
QObject::connect(watcher, &QFutureWatcher<int>::resultReadyAt, [](int index) {
qDebug() << " -> Result at index" << index << "is ready.";
});
// 當(dāng)所有任務(wù)完成時(shí)觸發(fā)
QObject::connect(watcher, &QFutureWatcher<int>::finished, [watcher]() {
qDebug() << "All concurrent tasks finished.";
// 獲取所有結(jié)果
QFuture<int> future = watcher->future();
for (int i = 0; i < future.resultCount(); ++i) {
qDebug() << " Final Result[" << i << "] =" << future.resultAt(i);
}
watcher->deleteLater();
QCoreApplication::quit(); // 退出程序
});
// 使用 QtConcurrent::run 啟動(dòng)并發(fā)任務(wù)
// 返回 QFuture 對象用于跟蹤狀態(tài)
QFuture<int> future = QtConcurrent::mapped(QList<int>() << 0 << 1 << 2 << 3 << 4, [](int id) -> int {
qDebug() << "Concurrent task" << id << "running in thread:" << QThread::currentThreadId();
QThread::msleep(1000); // 模擬耗時(shí)
return id * 100; // 直接返回結(jié)果
});
// 將 future 交給 watcher 監(jiān)控
watcher->setFuture(future);
return a.exec();
}代碼運(yùn)行結(jié)果:

四、對比小結(jié)
| 維度 | QThread | QThreadPool + QRunnable | QtConcurrent |
|---|---|---|---|
| 控制粒度 | 線程級(最強(qiáng)) | 任務(wù)級 | 數(shù)據(jù)級(最弱) |
| 開發(fā)效率 | 較低(需手動(dòng)管理生命周期) | 中等(需封裝任務(wù)類) | 極高(一行代碼/Lambda) |
| 事件循環(huán) | 支持(核心優(yōu)勢) | 不支持(默認(rèn)無) | 不支持 |
| 信號槽 | 完美支持 | 不原生支持 | 不支持(通過QFuture獲取結(jié)果) |
| 底層依賴 | 操作系統(tǒng) API | 依賴 QThread | 依賴 QThreadPool |
| 核心場景 | 常駐后臺、需事件循環(huán) | 大量獨(dú)立短任務(wù) | 數(shù)據(jù)集合的并行計(jì)算 |
從代碼看差異
- 代碼量與復(fù)雜度:
QThread代碼最臃腫。你需要管理QObject、QThread的創(chuàng)建、moveToThread、信號槽連接,以及極其重要的內(nèi)存清理邏輯(finished->deleteLater)。QRunnable適中。你需要繼承并重寫run(),最關(guān)鍵的是要自己想辦法把結(jié)果“傳出來”(如回調(diào)、QMetaObject::invokeMethod),容易寫出回調(diào)地獄。QtConcurrent最清爽。不用寫類,不用管線程,一行run或mapped搞定,配合QFutureWatcher獲取結(jié)果非常優(yōu)雅。
- 結(jié)果獲取方式:
QThread:通過信號槽,天然線程安全,完美融入 Qt 事件循環(huán)。QRunnable:通過回調(diào)/std::function,或者在run()里用QMetaObject::invokeMethod往主線程發(fā)信號,略顯生硬。QtConcurrent:通過 QFuture / QFutureWatcher,這是 Qt 提供的高層抽象,不僅能拿結(jié)果,還能監(jiān)聽進(jìn)度、取消任務(wù)。
- 生命周期與線程復(fù)用(重點(diǎn)體會(huì)):
QThread示例中,5 個(gè)任務(wù)創(chuàng)建了 5 個(gè)真實(shí)的系統(tǒng)線程,任務(wù)完成后線程退出銷毀。QRunnable和QtConcurrent示例中,我們設(shè)置了setMaxThreadCount(2)。你會(huì)在運(yùn)行輸出中發(fā)現(xiàn),5 個(gè)任務(wù)只創(chuàng)建了 2 個(gè)線程!前 2 個(gè)任務(wù)跑完后,線程沒死,接著跑后 2 個(gè),實(shí)現(xiàn)了線程復(fù)用,大幅節(jié)省了系統(tǒng)資源。
選擇建議:
- 如果你需要后臺一直跑(比如監(jiān)聽串口數(shù)據(jù)),用 QThread + moveToThread。
- 如果你有一堆零碎的獨(dú)立任務(wù)(比如批量解析 100 個(gè)文件),用 QThreadPool + QRunnable。
- 如果你只是想讓一段計(jì)算代碼在后臺跑別卡界面,或者要對一個(gè)列表里的數(shù)據(jù)并行處理,毫不猶豫選 QtConcurrent。
到此這篇關(guān)于Qt中多線程QThread、QThreadPool、QConCurrent三種方式對比分析的文章就介紹到這了,更多相關(guān)Qt 多線程QThread、QThreadPool、QConCurrent區(qū)別內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
淺談關(guān)于指針作為參數(shù)并改變它的值的問題
這篇文章介紹了關(guān)于指針作為參數(shù)并改變它的值的問題,有需要的朋友可以參考一下2013-10-10
C++學(xué)習(xí)貝葉斯分類器實(shí)現(xiàn)手寫數(shù)字識別示例解析
這篇文章主要介紹了在C++學(xué)習(xí)中如何采用貝葉斯分類器來實(shí)現(xiàn)手寫數(shù)字識別的示例及解析有需要的朋友可以借鑒參考下,希望能夠有所幫助2021-10-10
C++函數(shù)參數(shù)匹配規(guī)則示例小結(jié)
這篇文章主要介紹了C++函數(shù)參數(shù)匹配規(guī)則,本文通過示例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-08-08

