qt-C++筆記之QThread使用
——2024-05-26 下午
code review!
參考博文:
qt-C++筆記之使用QtConcurrent異步地執行槽函數中的內容,使其不阻塞主界面
qt-C++筆記之QThread使用
文章目錄
- qt-C++筆記之QThread使用
- 一:Qt中幾種多線程方法
- 1.1. 使用 `QThread` 和 Lambda
- 1.2. 使用 `QThread::create()`
- 1.3. 繼承 `QThread` 并重寫 `run()`
- 1.4. 使用 `QObject` 派生類與 `QThread`
- 1.5. 使用 `QtConcurrent::run()`
- 總結
- 二.方法一:繼承QThread并重寫其run()方法
- 2.1.代碼一:運行全局純函數
- 2.2.代碼二:運行全局純函數
- 2.3.代碼一和代碼二的區別
- 2.3.1 帶`input`參數的構造函數
- 2.3.2. 帶`input`和`parent`參數的構造函數
- 2.3.3 總結
- 2.4.代碼三:直接在run()方法中寫運行邏輯
- 2.5.代碼四:直接在run()方法中寫運行邏輯,并在QCoreApplication::quit()前執行一些打印
- 2.6.代碼五:通過繼承QThread的方式來運行一個純函數,并將參數通過類的成員變量傳遞給這個函數
- 2.7.代碼六:通過繼承QThread的方式來運行一個純函數,并將參數通過類的成員變量傳遞給這個函數
- 三.方法二:將任務放在QObject派生類中,并`moveToThread()`在QThread中運行這個QObject
- 3.1. 代碼一:運行成員函數例程,讓 Worker 類繼承自 QObject,然后使用一個 QThread 實例來在另一個線程中運行這個 Worker 對象
- 3.2. 代碼二:運行成員函數例程,讓 Worker 類繼承自 QObject,然后使用一個 QThread 實例來在另一個線程中運行這個 Worker 對象
- 四.方法三:直接使用 QThread 和 Lambda
- 五.方法四:`QThread::create(Qt 5.10 及以上)`
- 5.1.提要
- 5.1.1.QThread::create()代碼一
- 5.1.2.QThread::create()代碼二
- 六.方法五:`QtConcurrent::run()`
一:Qt中幾種多線程方法
五種不同的多線程實現方法在Qt中的特點和適用場景:
方法 | 描述 | 優點 | 缺點 | 適用場景 |
---|---|---|---|---|
直接使用 QThread 和 Lambda | 使用 QThread 對象和信號槽連接一個lambda表達式來執行代碼。 | 簡單快速;靈活定義啟動行為。 | 精細控制線程較少;線程復用不便。 | 快速實現簡單的后臺任務,不需要復用線程。 |
使用 QThread::create() | 使用 QThread::create() 創建并自動啟動線程來執行函數或lambda。 | 極簡代碼;自動線程管理。 | 對線程的控制較為有限;不易于復用。 | 適合快速啟動一次性后臺任務,不需要線程間交互。 |
繼承 QThread 并重寫 run() | 通過繼承 QThread 并重寫其 run() 方法來定義線程行為。 | 完全控制線程行為;能夠處理復雜邏輯。 | 實現復雜;易誤用(如直接操作GUI)。 | 需要精細控制線程行為或有復雜線程邏輯的場景。 |
使用 QObject 派生類與 QThread | 創建 QObject 派生類,并在移至 QThread 的對象上執行任務。 | 分離工作和線程管理;完全的信號和槽支持。 | 設置相對繁瑣;代碼量較多。 | 需要線程頻繁與主線程通信或任務較為復雜的場景。 |
使用 QtConcurrent::run() | 使用Qt并發模塊的 QtConcurrent::run() 在線程池中運行函數或成員函數。 | 簡單易用;自動管理線程池;減少資源消耗。 | 對線程控制較少;不適合特定線程管理需求。 | 適用于執行獨立的并發任務,不需要細粒度線程控制。 |
1.1. 使用 QThread
和 Lambda
這種方法直接使用 QThread
的實例,并通過信號和槽系統將lambda表達式綁定至 QThread::started
信號。這種方法允許靈活地定義線程開始時執行的代碼,而不需創建額外的類或使用 QtConcurrent
。它適合快速簡單的線程使用,尤其是當你不需要頻繁與主線程通信或管理復雜的線程生命周期時。
1.2. 使用 QThread::create()
QThread::create()
是Qt 5.10引入的一個便捷函數,它基本上是創建線程與綁定任務的簡化版。你只需要提供一個函數或lambda表達式,QThread::create()
會自動創建線程并在啟動線程時執行這個函數。
auto thread = QThread::create([](){// 執行一些任務
});
thread->start();
這種方法的優點是極其簡單,但它通常不適用于需要復雜線程管理或多次復用線程的場景。
1.3. 繼承 QThread
并重寫 run()
這是一種更傳統的方法,通過繼承 QThread
并重寫其 run()
方法實現。這種方法提供了最大的靈活性,允許你控制線程的準確行為,但也需要更多的代碼和復雜的錯誤處理。
class WorkerThread : public QThread
{
protected:void run() override {// 執行任務}
};
1.4. 使用 QObject
派生類與 QThread
這是Qt推薦的多線程使用方式。你創建一個 QObject
派生類來封裝工作任務,然后將這個對象的實例移動到 QThread
中。
class Worker : public QObject
{Q_OBJECT
public slots:void doWork() {// 執行任務}
};QThread *thread = new QThread();
Worker *worker = new Worker();
worker->moveToThread(thread);
connect(thread, &QThread::started, worker, &Worker::doWork);
thread->start();
這種方法適合需要線程與主線程頻繁交互的場景,因為它完全兼容Qt的信號與槽機制。
1.5. 使用 QtConcurrent::run()
QtConcurrent::run()
是處理并發運算的另一種高級方法,它可以非常方便地在后臺線程池中運行函數或成員函數。
QtConcurrent::run([](){// 執行某些操作
});
這種方法非常適合不需要細粒度控制線程行為的場景,且可以自動管理線程池,避免創建和銷毀線程的開銷。
總結
- 直接使用
QThread
和 Lambda:適合快速、一次性的簡單后臺任務。 - 使用
QThread::create()
:簡化版的線程創建和任務綁定,適合不需要復用線程的場景。 - 繼承
QThread
:適用于需要完全控制線程行為的復雜場景。 - 使用
QObject
派生類與QThread
:Qt推薦的方式,適合需要線程間頻繁通信的場景。 - 使用
QtConcurrent::run()
:適用于簡單并發任務,自動線程池管理,減少資源消耗。
二.方法一:繼承QThread并重寫其run()方法
2.1.代碼一:運行全局純函數
#include <QCoreApplication>
#include <QThread>
#include <QDebug>// 純函數的定義,這個函數只進行計算并返回結果,不涉及任何的狀態修改
int pureFunction(int x) {return x * x;
}// 創建一個線程類,用于運行純函數
class WorkerThread : public QThread {
public:WorkerThread(int input) : inputValue(input) {}protected:void run() override {int result = pureFunction(inputValue);qDebug() << "計算結果:" << result;}private:int inputValue;
};int main(int argc, char *argv[]) {QCoreApplication a(argc, argv);// 初始化一個線程對象,傳入參數5WorkerThread thread(5);// 連接線程的finished信號到QCoreApplication的quit槽,確保應用程序在線程結束后退出QObject::connect(&thread, &QThread::finished, &a, &QCoreApplication::quit);// 啟動線程thread.start();// 進入事件循環return a.exec();
}
運行
計算結果: 25
2.2.代碼二:運行全局純函數
#include <QCoreApplication>
#include <QThread>
#include <QDebug>// 全局純函數
void globalPureFunction(int param) {qDebug() << "運行在線程:" << QThread::currentThreadId();qDebug() << "接收到的參數:" << param;// 模擬一些處理過程QThread::sleep(2); // 假裝我們在做一些耗時的工作qDebug() << "處理完成";
}// 線程類
class WorkerThread : public QThread {int m_param;
public:WorkerThread(int param, QObject *parent = nullptr) : QThread(parent), m_param(param) {}protected:void run() override {globalPureFunction(m_param); // 在新線程中調用全局純函數}
};// 主函數
int main(int argc, char *argv[]) {QCoreApplication a(argc, argv);int inputValue = 42; // 示例參數值WorkerThread *thread = new WorkerThread(inputValue);thread->start(); // 啟動線程QObject::connect(thread, &QThread::finished, thread, &QObject::deleteLater);QObject::connect(thread, &QThread::finished, &a, &QCoreApplication::quit);return a.exec();
}
運行
運行在線程: 0x7fd2ea57f700
接收到的參數: 42
處理完成
2.3.代碼一和代碼二的區別
在Qt中,處理QThread
和其他繼承自QObject
的類時,構造函數中的parent
參數非常關鍵,它影響對象的內存管理和事件傳遞機制。讓我們比較一下這兩種構造函數的定義和它們的實際用途:
2.3.1 帶input
參數的構造函數
WorkerThread(int input) : inputValue(input) {}
這個構造函數只接受一個input
參數,并初始化成員變量inputValue
。這里沒有顯式地處理parent
參數:
- 作用:初始化
inputValue
。 - 內存管理:這個
WorkerThread
對象的生命周期需要顯式管理(比如通過在堆上創建和刪除,或者確保其作用域在使用中不會結束),因為沒有父對象來自動管理它。 - 適用場景:當你不需要將線程對象的生命周期與其他Qt對象關聯時,或者當你想要通過代碼顯式管理線程的生命周期時使用。
2.3.2. 帶input
和parent
參數的構造函數
WorkerThread(int input, QObject *parent = nullptr) : QThread(parent), inputValue(input) {}
這個構造函數接受一個input
參數和一個可選的parent
參數,默認為nullptr
。它在初始化列表中調用了QThread
的構造函數,傳遞了parent
參數:
- 作用:初始化
inputValue
并設置父對象。 - 內存管理:如果指定了父對象(
parent
不為nullptr
),這個WorkerThread
對象的生命周期將由其父對象自動管理(父對象銷毀時,它也會被銷毀)。如果parent
為nullptr
,則其生命周期需要手動管理。 - 適用場景:當你希望線程的生命周期與某個Qt對象(如窗口或其他組件)綁定時使用。這樣可以簡化內存管理,使線程的生命周期與其父對象相匹配。
2.3.3 總結
添加parent
參數的版本提供了更靈活的內存管理選項,允許線程對象以樹形層次結構中的一部分被管理,這對于復雜的Qt應用程序來說非常有用。沒有parent
參數的版本則簡單、直接,更適合生命周期管理相對明確或簡單的場合。在實際應用中,選擇哪種方式取決于你的具體需求和線程管理策略。
2.4.代碼三:直接在run()方法中寫運行邏輯
#include <QCoreApplication>
#include <QThread>
#include <iostream>// WorkerThread 類,繼承自 QThread
class WorkerThread : public QThread
{
public:WorkerThread(QObject *parent = nullptr) : QThread(parent) {}// 重載 run 方法void run() override {// 純函數任務內容for (int i = 0; i < 10; ++i) {std::cout << "工作線程運行中: " << i << std::endl;QThread::sleep(1); // 模擬耗時操作,每次循環暫停1秒}std::cout << "工作線程結束運行。" << std::endl;}
};int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);// 創建 WorkerThread 對象WorkerThread thread;// 連接線程完成信號到退出槽,確保線程完成后應用程序退出QObject::connect(&thread, &WorkerThread::finished, &a, &QCoreApplication::quit);// 啟動線程thread.start();// 進入 Qt 事件循環return a.exec();
}
運行
工作線程運行中: 0
工作線程運行中: 1
工作線程運行中: 2
工作線程運行中: 3
工作線程運行中: 4
工作線程運行中: 5
工作線程運行中: 6
工作線程運行中: 7
工作線程運行中: 8
工作線程運行中: 9
工作線程結束運行。
2.5.代碼四:直接在run()方法中寫運行邏輯,并在QCoreApplication::quit()前執行一些打印
#include <QCoreApplication>
#include <QThread>
#include <QDebug>// 純函數,作為線程任務
void runTask() {for (int i = 0; i < 10; ++i) {QThread::sleep(1); // 模擬耗時任務qDebug() << "工作在線程 " << QThread::currentThreadId() << " 中執行: " << i;}
}// 自定義的線程類
class TaskThread : public QThread {Q_OBJECT // 添加 Q_OBJECT 宏以支持信號和槽
public:TaskThread(QObject *parent = nullptr) : QThread(parent) {}protected:void run() override {runTask(); // 在新線程中運行純函數emit taskCompleted(); // 發射任務完成信號}signals:void taskCompleted();
};int main(int argc, char *argv[]) {QCoreApplication a(argc, argv);TaskThread thread;QObject::connect(&thread, &TaskThread::taskCompleted, &a, [&]() {qDebug() << "任務完成,信號在主線程 " << QThread::currentThreadId() << " 中被處理";QCoreApplication::quit(); // 完成后退出程序});qDebug() << "主線程ID: " << QThread::currentThreadId();thread.start(); // 啟動線程return a.exec();
}#include "main.moc" // 如果你不是使用 qmake,確保 moc 處理這個文件
運行
主線程ID: 0x7f7e73b0d780
工作在線程 0x7f7e6ef9d700 中執行: 0
工作在線程 0x7f7e6ef9d700 中執行: 1
工作在線程 0x7f7e6ef9d700 中執行: 2
工作在線程 0x7f7e6ef9d700 中執行: 3
工作在線程 0x7f7e6ef9d700 中執行: 4
工作在線程 0x7f7e6ef9d700 中執行: 5
工作在線程 0x7f7e6ef9d700 中執行: 6
工作在線程 0x7f7e6ef9d700 中執行: 7
工作在線程 0x7f7e6ef9d700 中執行: 8
工作在線程 0x7f7e6ef9d700 中執行: 9
任務完成,信號在主線程 0x7f7e73b0d780 中被處理
2.6.代碼五:通過繼承QThread的方式來運行一個純函數,并將參數通過類的成員變量傳遞給這個函數
#include <QCoreApplication>
#include <QThread>
#include <QDebug>// 純函數,有傳參
int square(int x) {return x * x;
}// 繼承QThread的類,重寫run()函數
class MyThread : public QThread {
protected:void run() override {// 調用純函數,并傳參int result = square(x);qDebug() << "Result:" << result;}public:int x;
};int main(int argc, char *argv[]) {QCoreApplication a(argc, argv);// 創建MyThread對象MyThread thread;// 在main函數中聲明參數,并傳入MyThread對象int param = 10;thread.x = param;// 啟動MyThread對象thread.start();// 等待MyThread對象結束thread.wait();return a.exec();
}
運行
Result: 100
2.7.代碼六:通過繼承QThread的方式來運行一個純函數,并將參數通過類的成員變量傳遞給這個函數
#include <QCoreApplication>
#include <QThread>
#include <QDebug>// 純函數的聲明
void pureFunction(int a, int b);// 繼承自 QThread 的類
class WorkerThread : public QThread {public:// 構造函數WorkerThread(int a, int b, QObject *parent = nullptr) : QThread(parent), m_a(a), m_b(b) {}protected:// 重寫 run 方法void run() override {// 在新線程中調用純函數qDebug() << "線程開始執行";pureFunction(m_a, m_b);qDebug() << "線程執行完成";}private:int m_a, m_b; // 成員變量,用于存儲傳遞給純函數的參數
};// 純函數的實現
void pureFunction(int a, int b) {// 執行一些計算或處理int result = a + b;qDebug() << "純函數執行結果:" << result;
}int main(int argc, char *argv[]) {QCoreApplication a(argc, argv);// 創建 WorkerThread 對象,傳遞參數給純函數WorkerThread thread(10, 20);// 啟動線程thread.start();// 等待線程執行完成thread.wait();return a.exec();
}
運行
線程開始執行
純函數執行結果: 30
線程執行完成
三.方法二:將任務放在QObject派生類中,并moveToThread()
在QThread中運行這個QObject
3.1. 代碼一:運行成員函數例程,讓 Worker 類繼承自 QObject,然后使用一個 QThread 實例來在另一個線程中運行這個 Worker 對象
// 包含必要的頭文件
#include <QCoreApplication>
#include <QThread>
#include <QDebug>// Worker 類定義
class Worker : public QObject {Q_OBJECTpublic:Worker() {}virtual ~Worker() {}public slots:void process() {// 輸出當前線程信息qDebug() << "Worker thread running in thread:" << QThread::currentThreadId();// 模擬耗時操作QThread::sleep(3);qDebug() << "Worker process completed.";emit finished();}signals:void finished();
};int main(int argc, char *argv[]) {QCoreApplication a(argc, argv);// 創建線程對象QThread workerThread;Worker worker;// 將 worker 對象移動到新建的線程worker.moveToThread(&workerThread);// 連接信號和槽QObject::connect(&workerThread, &QThread::started, &worker, &Worker::process);QObject::connect(&worker, &Worker::finished, &workerThread, &QThread::quit);QObject::connect(&worker, &Worker::finished, &worker, &Worker::deleteLater);QObject::connect(&workerThread, &QThread::finished, &workerThread, &QThread::deleteLater);// 啟動線程workerThread.start();// 運行事件循環int result = a.exec();// 等待線程結束workerThread.wait();return result;
}#include "main.moc"
運行
Worker thread running in thread: 0x7f1943a36700
Worker process completed.
double free or corruption (out)
15:43:36: The program has unexpectedly finished.
3.2. 代碼二:運行成員函數例程,讓 Worker 類繼承自 QObject,然后使用一個 QThread 實例來在另一個線程中運行這個 Worker 對象
#include <QCoreApplication>
#include <QThread>
#include <QDebug>// Worker類,負責執行實際的計算
class Worker : public QObject {Q_OBJECTpublic:Worker(int a, int b) : m_a(a), m_b(b) {}public slots:void process() {int result = m_a + m_b; // 簡單的加法計算qDebug() << "計算結果:" << result;emit finished();}signals:void finished();private:int m_a;int m_b;
};int main(int argc, char *argv[]) {QCoreApplication a(argc, argv);int value1 = 5, value2 = 3;Worker *worker = new Worker(value1, value2);QThread *thread = new QThread;// 將worker移動到線程worker->moveToThread(thread);// 連接信號和槽QObject::connect(thread, &QThread::started, worker, &Worker::process);QObject::connect(worker, &Worker::finished, thread, &QThread::quit);QObject::connect(worker, &Worker::finished, worker, &QObject::deleteLater);QObject::connect(thread, &QThread::finished, thread, &QObject::deleteLater);QObject::connect(thread, &QThread::finished, &a, &QCoreApplication::quit); // 確保應用退出// 啟動線程thread->start();return a.exec();
}#include "main.moc"
運行
計算結果: 8
四.方法三:直接使用 QThread 和 Lambda
#include <QCoreApplication>
#include <QThread>
#include <QDebug>// 純函數定義,用于計算兩個整數的和
int add(int a, int b) {return a + b;
}int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);// 創建線程對象QThread* thread = new QThread;int x = 3, y = 4;// 將任務移至線程,使用lambda表達式QObject::connect(thread, &QThread::started, [=]() mutable {int result = add(x, y);qDebug() << "The sum of" << x << "and" << y << "is" << result;thread->quit(); // 線程任務完成,請求退出線程});// 清理線程資源QObject::connect(thread, &QThread::finished, thread, &QObject::deleteLater);// 啟動線程thread->start();return a.exec();
}
運行
The sum of 3 and 4 is 7
五.方法四:QThread::create(Qt 5.10 及以上)
5.1.提要
-
直接在 QThread類中沒有提供直接創建并啟動線程執行特定函數的方法(例如 QThread::create 是 C++11 后Qt 5.10 添加的功能,如果您使用的Qt版本較舊,這一功能可能不可用)
-
在Qt中使用QThread來運行一個全局純函數是一個比較通用的任務,在Qt中,通常通過繼承QThread并重寫run()方法來實現這一點,但使用QThread的能力來直接啟動一個線程執行我們的函數。
5.1.1.QThread::create()代碼一
#include <QCoreApplication>
#include <QThread>
#include <QDebug>void performCalculation(int a, int b) {int result = a + b;qDebug() << "計算結果:" << result;
}int main(int argc, char *argv[]) {QCoreApplication app(argc, argv);QThread *thread = QThread::create(performCalculation, 5, 3);QObject::connect(thread, &QThread::finished, thread, &QObject::deleteLater);thread->start();return app.exec();
}
5.1.2.QThread::create()代碼二
#include <QCoreApplication>
#include <QThread>
#include <QDebug>// 全局純函數
void performCalculation(int a, int b) {int result = a + b; // 簡單的加法計算qDebug() << "計算結果:" << result;
}// 線程執行的函數
void runInThread(int a, int b) {// 創建線程對象QThread* thread = QThread::create([=](){performCalculation(a, b);});// 連接線程結束信號到刪除槽,確保資源被清理QObject::connect(thread, &QThread::finished, thread, &QObject::deleteLater);// 啟動線程thread->start();
}int main(int argc, char *argv[]) {QCoreApplication a(argc, argv);// 在線程中執行全局函數runInThread(5, 3);return a.exec();
}
六.方法五:QtConcurrent::run()
#include <QCoreApplication>
#include <QThread>
#include <QDebug>
#include <QtConcurrent>// 全局純函數
void performCalculation(int a, int b) {int result = a + b; // 簡單的加法計算qDebug() << "在線程" << QThread::currentThreadId() << "計算結果:" << result;
}int main(int argc, char *argv[]) {QCoreApplication app(argc, argv);// 使用QtConcurrent運行全局函數int value1 = 5, value2 = 3;QFuture<void> future = QtConcurrent::run(performCalculation, value1, value2);// 等待任務完成future.waitForFinished();return app.exec();
}