在Qt开发中,我们经常会遇到一些耗时操作,比如文件读写、网络请求、复杂计算等。如果把这些操作放在主线程(GUI线程)中执行,界面就会卡住,用户体验极差。这时候就需要引入多线程技术。
我最近重构了一个日志分析工具,原本单线程处理500MB的日志文件需要近30秒,界面完全冻结。改用多线程后,界面保持流畅,处理时间缩短到8秒左右。这就是多线程的威力。
最常见的方式是继承QThread并重写run()方法。这种方式功能强大但稍显复杂,需要创建新类,对于简单任务有点杀鸡用牛刀的感觉。
Qt提供了QtConcurrent命名空间,可以用高阶函数的方式实现多线程。这种方式代码简洁,但灵活性较差,适合简单的并行计算。
将QObject对象移动到新线程中,通过信号槽通信。这种方式比较灵活,但同样需要创建额外的类。
这是我们今天要重点介绍的方式,特别适合不需要频繁通信的简单任务。它最大的优点是不需要创建新类,代码非常简洁。
cpp复制// 1. 创建任务类(继承QRunnable)
class SimpleTask : public QRunnable {
public:
void run() override {
// 在这里执行耗时操作
qDebug() << "Task running in thread:" << QThread::currentThread();
}
};
// 2. 使用线程池执行任务
QThreadPool::globalInstance()->start(new SimpleTask);
从Qt 5.15开始,可以直接用Lambda创建任务:
cpp复制QThreadPool::globalInstance()->start([]{
qDebug() << "Lambda task in thread:" << QThread::currentThread();
});
cpp复制void doWork(const QString& param) {
qDebug() << "Processing:" << param;
}
QThreadPool::globalInstance()->start([=]{
doWork("some data");
});
假设我们需要批量压缩图片:
cpp复制QStringList imagePaths = {"1.jpg", "2.png", "3.bmp"};
foreach (const QString &path, imagePaths) {
QThreadPool::globalInstance()->start([=]{
QImage img(path);
if (!img.isNull()) {
img.save(path + ".compressed", "JPG", 50);
qDebug() << "Compressed:" << path;
}
});
}
计算斐波那契数列:
cpp复制int fibonacci(int n) {
if (n <= 1) return n;
return fibonacci(n-1) + fibonacci(n-2);
}
QFuture<int> future = QtConcurrent::run(fibonacci, 40);
qDebug() << "Result:" << future.result(); // 阻塞等待结果
cpp复制// 设置最大线程数
QThreadPool::globalInstance()->setMaxThreadCount(4);
// 获取活跃线程数
int active = QThreadPool::globalInstance()->activeThreadCount();
QRunnable本身不支持取消操作,可以通过标志位实现:
cpp复制class CancelableTask : public QRunnable {
public:
std::atomic<bool> stopped{false};
void run() override {
while(!stopped) {
// 执行任务
}
}
};
CancelableTask* task = new CancelableTask;
QThreadPool::globalInstance()->start(task);
// 需要取消时
task->stopped = true;
对于大数据处理,可以将任务分成小块:
cpp复制void processChunk(int start, int end) {
for (int i = start; i < end; ++i) {
// 处理数据
}
}
const int total = 1000000;
const int chunkSize = 10000;
for (int i = 0; i < total; i += chunkSize) {
QThreadPool::globalInstance()->start([=]{
processChunk(i, qMin(i + chunkSize, total));
});
}
使用QFutureWatcher监控任务完成:
cpp复制QFutureWatcher<void> watcher;
connect(&watcher, &QFutureWatcher<void>::finished, []{
qDebug() << "All tasks completed";
});
QList<QFuture<void>> futures;
for (int i = 0; i < 10; ++i) {
futures.append(QtConcurrent::run([]{
// 执行任务
}));
}
watcher.setFuture(QFuture<void>::waitForFinished(futures));
方便调试时识别线程:
cpp复制QThread::currentThread()->setObjectName("WorkerThread");
使用QDeadlineTimer检测可能的死锁:
cpp复制QMutex mutex;
QDeadlineTimer timer(1000); // 1秒超时
if (!mutex.tryLock(timer.remainingTime())) {
qWarning() << "Possible deadlock detected";
return;
}
使用QElapsedTimer测量执行时间:
cpp复制QElapsedTimer timer;
timer.start();
// 执行任务
qDebug() << "Time elapsed:" << timer.elapsed() << "ms";
优点:
缺点:
OpenMP适合数据并行,语法更简洁:
cpp复制#pragma omp parallel for
for (int i = 0; i < n; ++i) {
// 并行处理
}
但Qt方案更适合与GUI结合的场景。
在开发视频处理软件时,我使用QRunnable实现了帧并行处理:
关键代码片段:
cpp复制class FrameProcessor : public QRunnable {
public:
FrameProcessor(const VideoFrame& frame) : m_frame(frame) {}
void run() override {
applyFilters(m_frame);
emit frameProcessed(m_frame);
}
signals:
void frameProcessed(const VideoFrame& frame);
private:
VideoFrame m_frame;
};
// 使用时
FrameProcessor* processor = new FrameProcessor(frame);
connect(processor, &FrameProcessor::frameProcessed,
this, &VideoPlayer::showProcessedFrame);
QThreadPool::globalInstance()->start(processor);
这种设计使4K视频的实时滤镜处理成为可能,CPU利用率从30%提升到90%,处理速度提高3倍。
cpp复制QRunnable* task = new SimpleTask;
task->setAutoDelete(true);
QThreadPool::globalInstance()->start(task, priority); // priority从0(最高)到INT_MAX
使用QThreadStorage存储线程特定数据:
cpp复制QThreadStorage<QCache<QString, QImage>*> imageCache;
void processImage(const QString& path) {
if (!imageCache.hasLocalData()) {
imageCache.setLocalData(new QCache<QString, QImage>(100));
}
QImage* img = imageCache.localData()->object(path);
if (!img) {
img = new QImage(path);
imageCache.localData()->insert(path, img);
}
// 使用img
}
在QML中也可以通过WorkerScript实现多线程:
qml复制// WorkerScript.qml
import QtQuick 2.0
WorkerScript {
id: worker
source: "worker.js"
onMessage: console.log("Received:", messageObject.reply)
function send(data) {
worker.sendMessage({input: data})
}
}
// worker.js
WorkerScript.onMessage = function(message) {
// 处理数据
WorkerScript.sendMessage({reply: "Processed: " + message.input})
}
我在实际项目中发现,80%的多线程需求都可以用QRunnable+QThreadPool解决,无需创建额外的类。这种方式代码简洁,维护方便,特别适合中小规模的并行任务处理。