Qt 多线程之 QtConCurrent

1. QtConcurrent 类概述

QtConcurrent 是 Qt 提供的高级 API,位于 QtConcurrent 命名空间下。它是对 QThreadPoolQFuture 的高级封装,旨在简化多线程编程。

  • QtConcurrent 模块默认情况下并不自己创建线程,而是复用 QThreadPool::globalInstance()
  • 异步执行:任务被扔到线程池后,主线程立即返回,继续执行后续代码。
  • QFuture 代表了未来的结果,包括:状态查询,进度反馈,结果传递

它的最大优势是:你不需要手动创建和管理线程,也不需要处理线程底层的同步逻辑,只需关注要执行的函数。它把怎么开启线程怎么分配任务给哪个线程怎么回收线程这些脏活累活都交给了 QThreadPool,只留给你 QFuture 作为接口,让你专注于做什么而不是怎么做

1.1 准备工作

在使用 QtConcurrent 之前,必须在项目文件(.proCMakeLists.txt)中引入 concurrent 模块:

.pro (qmake):

1
QT += core concurrent

CMakeLists.txt: (以 Qt6 为例,Qt5 亦如此,修改版本号即可)

1
2
find_package(Qt6 REQUIRED COMPONENTS Core Concurrent)
target_link_libraries(my_target PRIVATE Qt6::Core Qt6::Concurrent)

头文件引入:

1
2
3
#include <QtConcurrent>
#include <QFuture>
#include <QFutureWatcher>

1.2 QFutureQFutureWatcher

1.2.1 QFuture 静默的结果搬运工

通俗地说,QFuture 就是一张提货单。当你下单一个异步任务(比如 “计算文件夹 SHA1”)时,商店(Qt 系统)不会立刻给你货(计算结果),因为货还在做。商店给你的就是这张 QFuture 提货单。你拿着这张单子,虽然还没拿到货,但你可以随时询问商店:“我的货好了没?”

具体能力(它能干什么?)

作为数据的容器,它主要提供了以下 API 栈:

  1. 查询状态isFinished(), isRunning(), isCanceled(),可以在代码里随时检查:

    1
    2
    3
    4
    // 函数原型, 条件成立返回 true, 否则返回 false
    bool QFuture::isFinished() const;
    bool QFuture::isRunning() const;
    bool QFuture::isCanceled() const;
    1
    2
    3
    4
    if (future.isFinished()) 
    {
    // 好了,可以拿东西了
    }
  2. 获取结果result(), resultAt(i), results()

    1
    2
    3
    4
    5
    6
    7
    // 函数原型
    template <typename U = T, typename = QtPrivate::EnableForNonVoid<U>>
    T QFuture::result() const;
    template <typename U = T, typename = QtPrivate::EnableForNonVoid<U>>
    T QFuture::resultAt(int index) const;
    template <typename U = T, typename = QtPrivate::EnableForNonVoid<U>>
    QList<T> QFuture::results() const;
    • 如果任务只返回一个T 类型的值(比如:QString),用 future.result()
    • 如果任务返回一堆值(mapped 中的 QList<T>),用 future.resultAt(0) 拿第一个。
    • 如果在主线程调用以上这些result()函数,但是计算还没做完,主线程会阻塞(卡死)直到结果出来。
  3. 控制任务cancel(), waitForFinished()

    1
    2
    3
    // 函数原型
    void QFuture::cancel();
    void QFuture::waitForFinished();
    • cancel():在提货单上盖个“作废”章,告诉后台别做了。
    • waitForFinished():你不想走了,就在商店门口一直等,直到货出来(这会阻塞当前线程)。

本质限制(为什么它不够用?)

QFuture 是一个轻量级的值类型(像 int 一样),它没有继承 QObject。这意味着:

  • 没有父对象,无法被 Qt 的对象树自动管理。
  • 不能发射信号(Signal/Slot)。
  • 不知道自己依附于哪个线程。

结论QFuture 很适合纯逻辑层的数据传递,但它无法“主动”告诉主界面“我好了”。你必须用轮询或者死等的方式去问它,这在 GUI 编程中很不方便。

1.2.2 QFutureWatcher:大嗓门的观察员

QFutureWatcher 的作用就是盯着 QFuture,一旦有动静,立刻通过信号槽大叫出来通知 UI。

具体工作原理(它是如何配合的?)

如果把 QFuture 比作“监控摄像头画面的原始数据流”,那么 QFutureWatcher 就是“连着摄像头的显示器”

  1. 建立连接
    你需要把一个 QFuture喂给 QFutureWatcher

    1
    2
    3
    4
    // 构造函数
    QFutureWatcher::QFutureWatcher(QObject *parent = nullptr);
    // 将 QFuture 对象设置给 QFutureWatcher
    void QFutureWatcher::setFuture(const QFuture<T> &future);
    1
    2
    QFutureWatcher<QString> watcher;
    watcher.setFuture(future); // Watcher 开始盯着这个 Future
  2. 信号转化(核心)
    QFutureWatcher 继承自 QObject,所以它拥有信号槽能力。它在后台默默监控 QFuture 的内部状态变化,一旦检测到变化,就发射对应的信号:

    1
    2
    3
    4
    [signal] void QFutureWatcher::canceled();
    [signal] void QFutureWatcher::finished();
    [signal] void QFutureWatcher::resultReadyAt(int index);
    [signal] void QFutureWatcher::progressValueChanged(int progressValue);
    • 内部产生了一个结果:发射 resultReadyAt(int index) 信号。
    • 内部整体做完了:发射 finished() 信号。
    • 内部更新了进度:发射 progressValueChanged(int value) 信号。
    • 内部被取消了:发射 canceled() 信号。
  3. 绑定 UI
    因为它能发射信号,你就可以用 Qt 最擅长的槽函数机制来响应:

    1
    2
    3
    4
    // 当 Watcher 喊“第 3 个文件算完了”,UI 就更新第 3 行
    connect(&watcher, &QFutureWatcher<QString>::resultReadyAt, this, [](int index){
    ui->tableWidget->setItem(index, 0, new QTableWidgetItem("完成"));
    });

为什么 GUI 程序必须用它?

因为GUI 是事件驱动的

  • 没有 Watcher 时:你的主线程必须在一个 while 循环里不断问 future.isFinished()。这是一种“轮询”模式,极其浪费 CPU,而且代码写起来很丑。
  • 有了 Watcher:你就可以放手不管了。后台线程算完一个,Watcher 会自动把一个事件投递到主线程的消息队列里。主线程此时正在处理界面绘制,等它空闲了,自然会发现这个事件,从而触发你的槽函数。这种方式不卡界面,也不会漏掉消息

1.2.3 总结

举个生动的例子:外卖点餐

  • QFuture (外卖单):
    • 手机 APP 上的订单页面。
    • 你可以不断刷新它看状态是“制作中”还是“配送中”(isRunning)。
    • 但如果你不刷新,它不会主动跳出来告诉你饭好了。
    • 如果你不刷新,只是一直盯着屏幕等,你什么也别想干了(waitForFinished 阻塞)。
  • QFutureWatcher (外卖员电话):
    • 它是连接商家(后台线程)和你(主线程)的桥梁。
    • 虽然商家在跑腿,但你不用一直刷新 APP。
    • 当饭做好了(resultReady),你会接到电话:“你好,你的饭到了”。
    • 你接起电话(slot),然后去拿饭(更新 UI)。
特性 QFuture QFutureWatcher
身份 数据容器 监控器 / 适配器
继承关系 纯 C++ 类 继承自 QObject
能力 存结果、查状态、取消任务 发射信号、暂停/继续、连接 UI
使用场景 后台纯逻辑处理、函数间传递结果 GUI 界面更新、多线程交互
依存关系 可以独立存在 必须 setFuture() 才能工作

一句话总结QFuture 负责在后台干活和存数据QFutureWatcher 负责在前台喊话和传信

2. 常用 API 详解与举例

QtConcurrent 提供了三个最核心的功能:运行单个函数、并发处理序列、并发规约。

2.1 运行单个函数 (QtConcurrent::run)

这是最常用的方式,用于在另一个线程中运行一个函数或成员方法。

1
2
3
4
template <typename T> 
QFuture<T> QtConcurrent::run(Function function, ...);
template <typename T>
QFuture<T> QtConcurrent::run(QThreadPool *pool, Function function, ...);
  • QThreadPool *pool :指定一个自定义的线程池,用来运行指定的任务。

  • Function function: 指定在子线程中执行的函数(或者可调用对象)

    可以是全局函数、静态成员函数、std::function,也可以是Lambda 表达式

  • ... 可变参数:传递给上面那个 function实参

    Qt 会将这些参数拷贝一份,传递给新线程去执行。

场景:后台执行耗时计算。

A. 运行普通函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 定义一个耗时函数
long long calculateFactorial(int number) {
long long result = 1;
for (int i = 1; i <= number; ++i) {
result *= i;
// 模拟耗时
QThread::msleep(10);
}
return result;
}

void example_run() {
// 在新线程中运行 calculateFactorial
// 返回值会保存在 QFuture 中
QFuture<long long> future = QtConcurrent::run(calculateFactorial, 10);

// 方式1:阻塞等待结果(适用于非GUI线程或必须等待的情况)
// future.waitForFinished();
// qDebug() << "Result:" << future.result();

// 方式2:在 GUI 应用中配合 QFutureWatcher 使用(非阻塞)
QFutureWatcher<long long>* watcher = new QFutureWatcher<long long>;
QObject::connect(watcher, &QFutureWatcher<long long>::finished, [&]() {
qDebug() << "Calculation done, Result:" << watcher->result();
watcher->deleteLater(); // 清理
});

watcher->setFuture(future);
}
B. 运行类的成员函数

需要注意,类对象在线程中的生命周期。建议使用 const 引用或指针,防止拷贝。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class DataProcessor : public QObject {
Q_OBJECT
public:
void heavyTask(const QString &data) {
qDebug() << "Processing:" << data << "in thread:" << QThread::currentThreadId();
QThread::sleep(2);
qDebug() << "Done";
}
};

// 使用方式
DataProcessor processor;
// 注意:如果 processor 在栈上,请确保主线程等待或 processor 生命周期足够长
// 此处 lambda 捕获 processor
auto future = QtConcurrent::run([&processor](){
processor.heavyTask("MemberFunction Test");
});

2.2 并发修改序列 (QtConcurrent::map)

对序列(如 QList)中的每一项应用一个函数。该函数直接修改原始项(引用传递),没有返回值

1
2
3
4
5
6
7
8
template <typename Sequence, typename MapFunctor> 
QFuture<void> QtConcurrent::map(Sequence &&sequence, MapFunctor &&function);
template <typename Iterator, typename MapFunctor>
QFuture<void> QtConcurrent::map(Iterator begin, Iterator end, MapFunctor &&function);
template <typename Sequence, typename MapFunctor>
QFuture<void> QtConcurrent::map(QThreadPool *pool, Sequence &&sequence, MapFunctor &&function);
template <typename Iterator, typename MapFunctor>
QFuture<void> QtConcurrent::map(QThreadPool *pool, Iterator begin, Iterator end, MapFunctor &&function);
  1. 这两个参数的作用是告诉 map 函数,你需要对哪一份数据进行修改。出现了两种形式:
    • Sequence &&sequence (容器整体)
      • 含义:直接传入一个容器对象。
      • 支持类型:通常是 QListQVectorstd::vector 等 STL 或 Qt 容器。
      • 用法:把整个 list 传进去,map 会自动把它拆开分发给每个线程。
      • T&& (未定引用类型(万能引用类型)):意味着它可以接受临时对象,当然也可以接受普通对象。因此,该参数可用是左值也可以是右值。
    • Iterator begin, Iterator end (迭代器范围)
      • 含义:传入一个“半开区间” [begin, end),表示从哪里开始处理,到哪里结束(不包含 end)。
      • 支持类型:任何符合 STL 标准的迭代器(比如 vector.begin())。
      • 用法:这比传容器更灵活。比如你只想修改 QList 的前 5 个元素,就可以传 list.begin()list.begin() + 5
  2. 操作参数(要做什么?):MapFunctor &&function
    • 含义:这是一个函数(或 Lambda 表达式),也就是要对每个元素执行的“操作”。
    • 特征:这个函数必须有且仅有一个参数
    • 参数类型:这个参数的类型必须是容器元素的引用(例如 int&QString&),或者是可以修改对象的类型。
    • 为什么是引用?因为map 函数的目的是“原地修改”
      • 如果你的函数是 void func(int x) { x = 10; },那么修改无效(改的是副本)。
      • 必须是 void func(int &x) { x = 10; },这样容器里的值才会真的发生改变。
  3. 环境参数(在哪里执行?- 可选):QThreadPool *pool
    • 含义:指定运行任务的线程池。
    • 作用:如果你不传这个参数(用前两个函数),任务会扔到 Qt 的全局线程池。如果传了这个参数,任务会在指定的线程池里执行。

场景:批量修改图片的属性信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct Image {
int size;
QString name;
};

// 修改函数:参数必须是非 const 引用
void scaleImage(Image &img) {
img.size *= 2; // 逻辑:放大图片
qDebug() << "Scaled" << img.name << "to" << img.size;
}

void example_map() {
QList<Image> images = { {10, "A.jpg"}, {20, "B.jpg"}, {30, "C.jpg"} };

// QtConcurrent::map 会自动在多个线程中调用 scaleImage(images[i])
QFuture<void> future = QtConcurrent::map(images, scaleImage);

// 等待所有修改完成
future.waitForFinished();

// 注意:map 直接修改了原始列表 images
}

2.3 并发处理并保留结果 (QtConcurrent::mapped)

map 类似,但操作函数不修改原项,而是返回一个新值。mapped 会生成一个新的 QList

1
2
3
4
5
6
7
8
template <typename Sequence, typename MapFunctor> 
QFuture<QtPrivate::MapResultType<Sequence, MapFunctor>> QtConcurrent::mapped(Sequence &&sequence, MapFunctor &&function);
template <typename Iterator, typename MapFunctor>
QFuture<QtPrivate::MapResultType<Iterator, MapFunctor>> QtConcurrent::mapped(Iterator begin, Iterator end, MapFunctor &&function);
template <typename Sequence, typename MapFunctor>
QFuture<QtPrivate::MapResultType<Sequence, MapFunctor>> QtConcurrent::mapped(QThreadPool *pool, Sequence &&sequence, MapFunctor &&function);
template <typename Iterator, typename MapFunctor>
QFuture<QtPrivate::MapResultType<Iterator, MapFunctor>> QtConcurrent::mapped(QThreadPool *pool, Iterator begin, Iterator end, MapFunctor &&function);

这四个函数是 QtConcurrent::mapped 的重载形式。相较于QtConcurrent::map函数二者区别为:

  • map原地修改容器。
  • mapped映射生成新数据,容器中的数据不变

QtConcurrent::mapped 的大部分参数含义与 QtConcurrent::map 相同。

  • Sequence &&sequence (容器整体)
    • 含义:传入整个容器(如 QList, QVector, std::vector)。
    • 行为:函数会读取这个容器中的每一个元素。注意:原容器不会发生任何变化。
  • Iterator begin, Iterator end (迭代器范围)
    • 含义:传入起始和结束迭代器(半开区间 [begin, end))。
    • 行为:处理该范围内的所有元素。同样,迭代器指向的数据是只读的(除非你强制转换,但不推荐)
  • MapFunctor &&function
    • 含义:转换函数。输入“旧元素”,输出“新元素”。
    • 对该函数的要求:
      • 它接受一个参数(即容器中的元素类型)。
      • 它返回一个值(即转换后的结果类型)。
    • 关键点(与 map 的核心区别):
      • 参数不需要是引用,因为它只是读取数据。
      • 例如:int square(int x) { return x * x; }。输入 2,返回 4。
  • QThreadPool *pool:指定线程池。如果不传,则使用全局线程池。

返回值类型(模板部分的解释),你可能会注意到返回值写得很复杂:

1
QFuture<QtPrivate::MapResultType<Sequence, MapFunctor>>
  • QFuture<...>
    • 因为计算是异步的,所以返回一个 QFuture 对象。
    • 可以通过 future.results() 获取最终计算出来的新容器。
  • QtPrivate::MapResultType<...>
    • 这是一个 Qt 内部的类型推导魔法。
    • 它的意思是:根据提供的输入容器类型和函数返回值类型,自动推断出最终的容器类型
    • 举例:
      • 如果输入 QList<int>,函数返回 int,结果就是 QList<int>
      • 如果输入 QList<int>,函数返回 QString,结果就是 QList<QString>
      • 如果输入 std::vector<double>,函数返回 int,结果就是 std::vector<int>

场景:读取一堆文件内容,并对数据进行计算。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 翻倍函数:参数是 const,返回新值
int doubleValue(int val) {
QThread::usleep(100); // 模拟耗时
return val * 2;
}

void example_mapped() {
QList<int> numbers = {1, 2, 3, 4, 5};

// mapped 自动将返回值收集起来
QFuture<int> future = QtConcurrent::mapped(numbers, doubleValue);

future.waitForFinished();

// 结果是一个新的列表
QList<int> results = future.results();
qDebug() << results; // 输出: 2, 4, 6, 8, 10
}

2.4 并发归约 (QtConcurrent::mappedReduced)

这是 Map-Reduce 模式的实现。先并发处理每一项,然后将所有结果处理并最终合并成一个结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
template <typename ResultType, typename Sequence, typename MapFunctor, typename ReduceFunctor> 
QFuture<ResultType> QtConcurrent::mappedReduced(Sequence &&sequence, MapFunctor &&mapFunction, ReduceFunctor &&reduceFunction, QtConcurrent::ReduceOptions reduceOptions = ReduceOptions(UnorderedReduce|SequentialReduce));
template <typename ResultType, typename Iterator, typename MapFunctor, typename ReduceFunctor>
QFuture<ResultType> QtConcurrent::mappedReduced(Iterator begin, Iterator end, MapFunctor &&mapFunction, ReduceFunctor &&reduceFunction, QtConcurrent::ReduceOptions reduceOptions = ReduceOptions(UnorderedReduce | SequentialReduce));
template <typename ResultType, typename Sequence, typename MapFunctor, typename ReduceFunctor, typename InitialValueType>
QFuture<ResultType> QtConcurrent::mappedReduced(Sequence &&sequence, MapFunctor &&mapFunction, ReduceFunctor &&reduceFunction, InitialValueType &&initialValue, QtConcurrent::ReduceOptions reduceOptions = ReduceOptions(UnorderedReduce|SequentialReduce));
template <typename ResultType, typename Iterator, typename MapFunctor, typename ReduceFunctor, typename InitialValueType>
QFuture<ResultType> QtConcurrent::mappedReduced(Iterator begin, Iterator end, MapFunctor &&mapFunction, ReduceFunctor &&reduceFunction, InitialValueType &&initialValue, QtConcurrent::ReduceOptions reduceOptions = ReduceOptions(UnorderedReduce|SequentialReduce));
template <typename ResultType, typename Sequence, typename MapFunctor, typename ReduceFunctor>
QFuture<ResultType> QtConcurrent::mappedReduced(QThreadPool *pool, Sequence &&sequence, MapFunctor &&mapFunction, ReduceFunctor &&reduceFunction, QtConcurrent::ReduceOptions reduceOptions = ReduceOptions(UnorderedReduce|SequentialReduce));
template <typename ResultType, typename Iterator, typename MapFunctor, typename ReduceFunctor>
QFuture<ResultType> QtConcurrent::mappedReduced(QThreadPool *pool, Iterator begin, Iterator end, MapFunctor &&mapFunction, ReduceFunctor &&reduceFunction, QtConcurrent::ReduceOptions reduceOptions = ReduceOptions(UnorderedReduce|SequentialReduce));
template <typename ResultType, typename Sequence, typename MapFunctor, typename ReduceFunctor, typename InitialValueType>
QFuture<ResultType> QtConcurrent::mappedReduced(QThreadPool *pool, Sequence &&sequence, MapFunctor &&mapFunction, ReduceFunctor &&reduceFunction, InitialValueType &&initialValue, QtConcurrent::ReduceOptions reduceOptions = ReduceOptions(UnorderedReduce|SequentialReduce));
template <typename ResultType, typename Iterator, typename MapFunctor, typename ReduceFunctor, typename InitialValueType>
QFuture<ResultType> QtConcurrent::mappedReduced(QThreadPool *pool, Iterator begin, Iterator end, MapFunctor &&mapFunction, ReduceFunctor &&reduceFunction, InitialValueType &&initialValue, QtConcurrent::ReduceOptions reduceOptions = ReduceOptions(UnorderedReduce|SequentialReduce));

QtConcurrent::mappedReducedQtConcurrent 中最强大、最灵活的函数之一。它的核心思想是 Map-Reduce(映射-归约) 模式:

  1. Map:把一堆输入数据,转换成中间结果。
  2. Reduce:把所有的中间结果,合并(汇总)成最终的一个结果。

这在处理求和、统计、拼接字符串等需要将大量数据合并为一个值的任务时非常有用。

下面详细解释参数和返回值。

2.4.1 参数详解

我们可以将参数分为五类来理解:

A. 结果类型(显式指定)

template <typename ResultType>

  • 含义:这是模板参数(不是函数调用参数),写在尖括号里。
  • 作用:你必须明确告诉 Qt,最终算出来的结果是什么类型。
  • 示例:如果要算所有数字的和,这里就是 intdouble;如果要把所有字符串拼起来,这里就是 QString

B. 原始数据(输入端)

这组参数与 map/mapped 完全一样,用于提供原材料(原始数据)。

  • Sequence &&sequence:整个容器。
  • Iterator begin, Iterator end:迭代器范围。

C. Map 函数(变换逻辑:怎么做第一步)

  • MapFunctor &&mapFunction
    • 含义:用于处理容器中每一个元素的函数。
    • 输入:容器中的一个元素。
    • 输出:一个中间结果(这个中间结果稍后会被 Reduce 函数处理)。
    • 注意:不要在这里修改原容器,这里是做转换的。

D. Reduce 函数(合并逻辑:怎么做第二步)

  • ReduceFunctor &&reduceFunction

    • 含义:将 Map 产生的“中间结果”合并到“最终结果”中的函数。

    • 对该函数的要求:

      1
      void reduce(T &result, const U &intermediate);
      • 第一个参数 result:这是累积结果的引用(比如当前的累加和)。函数会直接修改这个值
      • 第二个参数 intermediate:这是 Map 函数刚刚吐出来的中间结果(只读)。
    • 示例:

      1
      2
      3
      4
      // 1. 如果是求和:
      void reduce(int &sum, int value) { sum += value; };
      // 2. 如果是拼字符串:
      void reduce(QString &str, const QString &part) { str += part; };

E. 初始值(可选)

  • InitialValueType &&initialValue
    • 含义:在开始 Reduce 之前,那个“累积结果”(即上面 Reduce 函数的第一个参数)的初始值是什么。
    • 作用:如果不传这个参数,Qt 会使用 ResultType 的默认构造函数初始化(比如 int 默认是 0,QString 默认是空)。
    • 场景:如果你想求和,但希望初始值是 100 而不是 0,就可以传这个。

F. 线程池(可选)

  • QThreadPool *pool
    • 位置:参数列表的最前面。
    • 作用:指定在哪个线程池运行,不传则用全局默认池。

G. 归约选项(可选,默认存在)

  • QtConcurrent::ReduceOptions reduceOptions

    • 默认值:UnorderedReduce | SequentialReduce

    • 作用:控制 Reduce 的顺序和行为。

    • 常用值:

      • OrderedReduce:保证最终结果按原始顺序组合(通常慢一些)。
      • UnorderedReduce:不保证顺序(通常快一些)。
      • SequentialReduce:强制按顺序一个一个归约(无法利用多核并行归约)。

2.4.2 返回值详解

  • QFuture<ResultType>
    • 含义:一个代表“未来计算结果”的对象。
    • ResultType:这就是在模板参数里指定的那个最终结果的类型。
    • 用法举例:通过 future.result()获取最终汇总后的那一个值。

场景:处理多个独立字符串,并拼接所有字符串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Map 函数:处理单个项,返回中间结果
QString toLowerString(const QString &str) {
return str.toLower();
}

// Reduce 函数:将中间结果 result 累加到 final 变量中
// 注意:第一个参数是累积结果的引用,第二个参数是 map 返回的单个结果
void accumulateStrings(QString &result, const QString &intermediate) {
result += intermediate + " ";
}

void example_mappedReduced() {
QList<QString> words = {"Hello", "WORLD", "Qt", "CONCURRENT"};

// 将所有单词转为小写并拼接到一个字符串中
QFuture<QString> future = QtConcurrent::mappedReduced(words, toLowerString, accumulateStrings);

future.waitForFinished();
qDebug() << "Final String:" << future.result();
// 输出大概类似: "hello world qt concurrent " (顺序取决于线程执行速度)
}

2.5 限制线程数与取消任务

限制线程数

QtConcurrent 默认使用全局线程池(QThreadPool::globalInstance()),默认线程数通常等于 CPU 核心数。如果你想限制并发线程数,可以使用 QThreadPool

1
2
3
4
5
6
7
8
// 创建一个自定义的池子,限制最大线程数为 2
QThreadPool pool; // 注意: 不能是栈对象
pool.setMaxThreadCount(2);

// 将 pool 传递给 run
QFuture<void> future = QtConcurrent::run(&pool, [](){
// 任务代码
});

取消任务

QFuture 提供了 cancel() 方法。注意,cancel 并不是强制杀死线程(强制杀死线程在 C++ 中很危险且不推荐),它只是设置一个标志位,指示跳过尚未开始的任务。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 假设有一个非常长的列表
QList<int> bigList = ...;
QFuture<void> future = QtConcurrent::map(bigList, [](int &val){
// 模拟耗时
QThread::sleep(1);
val += 1;
});

// 用户点击了取消按钮
future.cancel();
// 注意:已经开始执行的那些任务还是会跑完,但还没开始的任务会被跳过

future.waitForFinished();

2.6 注意事项

  1. 共享数据的互斥QtConcurrent 虽然方便,但它只是帮你把函数扔到了不同线程。如果多个并发函数同时读写同一个全局变量或共享对象,你依然需要使用 QMutexQReadWriteLock 进行保护。
  2. UI 操作警告:与所有多线程一样,严禁QtConcurrent::run 执行的函数中直接操作 UI(如 setText)。请通过信号槽转发回主线程。
  3. QObject 子线程问题:不要试图将 QObject 派生类的移动传递给 QtConcurrentQtConcurrent 适用于函数式编程模型,而不是对象生命周期管理。
  4. 异常处理:如果在 QtConcurrent 执行的函数中抛出了异常,该异常会被 Qt 捕获并存储在 QFuture 中。主线程通过 future.result() 时,异常会在主线程重新抛出(或者在 Qt 6 中通过 future.isCanceled() 等状态判断)。建议尽量使用返回值(如 bool success)来通知错误,而不是抛出异常。

2.7. 案例:批量文件处理

假设我们要做一个功能:计算一堆大文件的 MD5 值(非常耗时),并在进度条显示或列表显示结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <QtConcurrent>
#include <QFile>
#include <QCryptographicHash>
#include <QDebug>

// 1. 定义纯计算函数(无 UI 相关)
QString calculateFileMd5(const QString &filePath) {
QFile file(filePath);
if (!file.open(QIODevice::ReadOnly)) {
return QString("Error: Cannot open");
}

QCryptographicHash hash(QCryptographicHash::Md5);
if (hash.addData(&file)) {
return QString(hash.result().toHex());
}
return QString("Error: Failed");
}

void processFiles(const QStringList &fileList) {
// 2. 启动并发计算
// 使用 blockingMapped 也可以(阻塞式),但为了 UI 响应,我们使用非阻塞 mapped
QFuture<QString> future = QtConcurrent::mapped(fileList, calculateFileMd5);

// 3. 使用 Watcher 监控进度
QFutureWatcher<QString>* watcher = new QFutureWatcher<QString>();

QObject::connect(watcher, &QFutureWatcher<QString>::resultReadyAt, this, [&](int index) {
// 每完成一个文件,发出一个结果
qDebug() << "File" << fileList.at(index) << "MD5:" << watcher->resultAt(index);
// TODO: 此处可以进行 UI 的更新
});

QObject::connect(watcher, &QFutureWatcher<QString>::finished, this, [=]() {
qDebug() << "All files processed.";
watcher->deleteLater();
});

watcher->setFuture(future);
}

通过 QtConcurrent::mapped + QFutureWatcher,你只需要几行代码就实现了高性能的多线程文件处理,并且完美避开了手动管理线程的麻烦。