以下是一個結合 std::shared_ptr
和 Qt 線程池(QThreadPool
)的完整案例,展示了如何在多線程任務中安全管理資源,避免內存泄漏。
案例場景
- 任務目標:在后臺線程中處理一個耗時的圖像檢測任務,任務對象通過
std::shared_ptr
管理。 - 關鍵需求:
- 確保任務對象在任務完成后自動釋放。
- 支持跨線程信號槽通信,傳遞處理結果。
- 避免線程池與智能指針所有權沖突。
完整代碼
1. 自定義任務類(繼承自 QRunnable
)
// MyTask.h
#pragma once
#include <QRunnable>
#include <QObject>
#include <memory>
#include <QImage>class MyTask : public QObject, public QRunnable
{Q_OBJECT
public:MyTask(const QImage& inputImage);// 任務執行入口void run() override;// 設置任務完成后回調(通過信號)void setResultCallback(std::function<void(const QImage&)> callback);signals:// 任務完成信號,傳遞處理后的圖像void taskFinished(const QImage& result);private:QImage m_inputImage;std::function<void(const QImage&)> m_callback;
};
// MyTask.cpp
#include "MyTask.h"
#include <QDebug>MyTask::MyTask(const QImage& inputImage) : m_inputImage(inputImage)
{// 禁用 QRunnable 的自動刪除setAutoDelete(false);
}void MyTask::run()
{qDebug() << "Task started in thread:" << QThread::currentThreadId();// 模擬耗時操作(例如圖像處理)QImage processedImage = m_inputImage.mirrored(true, false);// 發送完成信號(跨線程)emit taskFinished(processedImage);// 或者調用回調函數if (m_callback) {m_callback(processedImage);}
}void MyTask::setResultCallback(std::function<void(const QImage&)> callback)
{m_callback = callback;
}
2. 主窗口類(使用線程池和 shared_ptr
)
// MainWindow.h
#pragma once
#include <QMainWindow>
#include <QThreadPool>
#include <memory>class MainWindow : public QMainWindow
{Q_OBJECT
public:MainWindow(QWidget *parent = nullptr);~MainWindow();private slots:// 處理任務完成信號void handleTaskFinished(const QImage& result);private:QThreadPool* m_threadPool;
};
// MainWindow.cpp
#include "MainWindow.h"
#include "MyTask.h"
#include <QPushButton>
#include <QDebug>MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), m_threadPool(new QThreadPool(this))
{// 設置線程池最大線程數m_threadPool->setMaxThreadCount(4);// 創建一個測試按鈕,點擊后提交任務QPushButton* btn = new QPushButton("Run Task", this);connect(btn, &QPushButton::clicked, [this]() {// 1. 創建任務對象并用 shared_ptr 管理QImage inputImage(800, 600, QImage::Format_RGB32);inputImage.fill(Qt::green);std::shared_ptr<MyTask> task = std::make_shared<MyTask>(inputImage);// 2. 連接任務完成信號到主線程的槽connect(task.get(), &MyTask::taskFinished, this, &MainWindow::handleTaskFinished, Qt::QueuedConnection); // 確保跨線程安全// 3. 提交任務到線程池(傳遞原始指針,但所有權由 shared_ptr 控制)m_threadPool->start(task.get());// 4. 使用 Lambda 捕獲 shared_ptr,確保任務完成后釋放資源task->setResultCallback([task](const QImage& result) {qDebug() << "Task callback executed. Ref count:" << task.use_count();});});
}void MainWindow::handleTaskFinished(const QImage& result)
{qDebug() << "Task finished. Result size:" << result.size();
}MainWindow::~MainWindow()
{// 等待所有任務完成m_threadPool->waitForDone();
}
3. 主函數
// main.cpp
#include "MainWindow.h"
#include <QApplication>int main(int argc char *argv[])
{QApplication a(argc, argv);// 注冊自定義類型(若需要傳遞復雜類型)// qRegisterMetaType<MyData>("MyData");MainWindow w;w.show();return a.exec();
}
關鍵機制解釋
-
禁用自動刪除:
setAutoDelete(false); // 在 MyTask 構造函數中
- 阻止
QThreadPool
自動刪除任務對象,避免與shared_ptr
沖突。
- 阻止
-
通過
shared_ptr
管理生命周期:std::shared_ptr<MyTask> task = std::make_shared<MyTask>(inputImage);
shared_ptr
確保任務對象在最后一個引用消失時自動釋放。
-
信號槽跨線程通信:
connect(task.get(), &MyTask::taskFinished, this, &MainWindow::handleTaskFinished, Qt::QueuedConnection);
- 使用
Qt::QueuedConnection
確保信號跨線程安全傳遞。
- 使用
-
Lambda 捕獲
shared_ptr
:task->setResultCallback([task](const QImage& result) {qDebug() << "Ref count:" << task.use_count(); });
- Lambda 表達式捕獲
task
會遞增引用計數,確保任務執行期間對象存活。
- Lambda 表達式捕獲
運行流程
- 用戶點擊按鈕,創建任務并用
shared_ptr
管理。 - 任務被提交到線程池,線程池調用
run()
執行耗時操作。 - 任務完成后,發送
taskFinished
信號或調用回調。 - 主線程接收結果并處理。
- 當所有引用(
shared_ptr
)釋放后,任務對象自動銷毀。
注意事項
- 線程安全設計:
- 如果任務內部訪問共享數據,需使用
QMutex
或QReadWriteLock
保護。
- 如果任務內部訪問共享數據,需使用
- 避免循環引用:
- 如果任務對象持有指向主窗口的指針,需使用原始指針或
weak_ptr
,避免shared_ptr
循環引用導致內存泄漏。
- 如果任務對象持有指向主窗口的指針,需使用原始指針或
- 性能優化:
- 如果頻繁創建任務,可復用任務對象或使用對象池減少內存分配開銷。
通過此案例,您可以安全地在 Qt 線程池中使用 std::shared_ptr
,確保資源生命周期正確管理。