以下是一个结合 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
,确保资源生命周期正确管理。