本页内容

并发 Map 和 Map-Reduce

QtConcurrent::map()、QtConcurrent::mapped() 和 QtConcurrent::mappedReduced() 函数会对序列(例如QList )中的项并行执行计算。 QtConcurrent::map() 对序列进行就地修改,QtConcurrent::mapped() 返回一个包含修改后内容的新序列,而 QtConcurrent::mappedReduced() 返回单一结果。

这些函数是 Qt Concurrent 框架的一部分。

上述每个函数都有一个阻塞版本,该版本返回最终结果,而不是QFuture 。其使用方式与异步版本相同。

QList<QImage> images = ...;

// Each call blocks until the entire operation is finished.
QList<QImage> future = QtConcurrent::blockingMapped(images, scaled);

QtConcurrent::blockingMap(images, scale);

QImage collage = QtConcurrent::blockingMappedReduced(images, scaled, addToCollage);

请注意,上述结果类型并非QFuture 对象,而是真正的结果类型(在本例中,分别为QList<QImage>和QImage)。

优化包含

如果您包含<QtConcurrent> 头文件,整个Qt Concurrent 模块以及整个Qt Core 模块都会被包含进来,这可能会增加编译时间和二进制文件的大小。若要使用QtConcurrent::map()、QtConcurrent::mapped() 和QtConcurrent::mappedReduced() 函数,您可以包含一个更具体的头文件:

#include <QtConcurrentMap>

并发映射

QtConcurrent::mapped() 接受一个输入序列和一个映射函数。随后,该映射函数将针对序列中的每个项被调用,并返回一个包含映射函数返回值的新序列。

map 函数必须采用以下形式:

U function(const T &t);

T 和 U 可以是任意类型(甚至可以是同一类型),但 T 必须与序列中存储的类型相匹配。该函数返回经过修改或映射后的内容。

此示例演示了如何对序列中的所有项应用缩放函数:

QImage scaled(const QImage &image)
{
    return image.scaled(100, 100);
}

QList<QImage> images = ...;
QFuture<QImage> thumbnails = QtConcurrent::mapped(images, scaled);

映射的结果可通过 `QFuture` 获取。有关如何在应用程序中使用 `QFuture ` 的更多信息,请参阅QFuture 和QFutureWatcher 中的文档。

若需就地修改序列,请使用 QtConcurrent::map()。此时,map 函数必须采用以下形式:

U function(T &t);

请注意,map 函数的返回值和返回类型均不会被使用。

使用 QtConcurrent::map() 与使用 QtConcurrent::mapped() 类似:

void scale(QImage &image)
{
    image = image.scaled(100, 100);
}

QList<QImage> images = ...;
QFuture<void> future = QtConcurrent::map(images, scale);

由于序列是在原地修改的,因此 QtConcurrent::map() 不会通过 `QFuture` 返回任何结果。不过,您仍然可以使用 `QFuture ` 和 `QFutureWatcher ` 来监控映射操作的状态。

并发映射与延续

调用 QtConcurrent::mapped() 的结果是一个包含多个结果的 `QFuture `。当将 `.then() ` 延续函数附加到此类 `QFuture` 上时,请确保使用将 `QFuture ` 作为参数的延续函数,否则只有第一个结果会被处理:

autoprocess= [](intval) {
    returnval* 2;
};

QList<int>inputs {1, 2, 3};
autobadFuture=QtConcurrent::mapped(inputs,process)
                         .then([](intval) {
                             qDebug() << val;
                         });

autogoodFuture=QtConcurrent::mapped(inputs,process)
                          .then([](QFuture<int>f) {
                              for(autor : f.results()) {
                                  qDebug() << r;
                              }
                          });

在此示例中,badFuture 只会打印一个结果,而goodFuture 会打印所有结果。

并发 Map-Reduce

QtConcurrent::mappedReduced() 与 QtConcurrent::mapped() 类似,但它不会返回包含新结果的序列,而是通过 reduce 函数将结果合并为单一值。

reduce 函数必须采用以下形式:

V function(T &result, const U &intermediate)

其中 T 是最终结果的类型,U 是映射函数的返回类型。请注意,归约函数的返回值和返回类型均不会被使用。

请按如下方式调用 QtConcurrent::mappedReduced():

void addToCollage(QImage &collage, const QImage &thumbnail)
{
    QPainter p(&collage);
    static QPoint offset = QPoint(0, 0);
    p.drawImage(offset, thumbnail);
    offset += ...;
}

QList<QImage> images = ...;
QFuture<QImage> collage = QtConcurrent::mappedReduced(images, scaled, addToCollage);

对于映射函数返回的每个结果,都会调用一次 reduce 函数,该函数应将中间结果合并到result变量中。QtConcurrent::mappedReduced() 保证每次只有一个线程会调用 reduce,因此无需使用互斥锁来锁定 result 变量。QtConcurrent::ReduceOptions 枚举提供了一种控制归约操作顺序的方法。如果使用QtConcurrent::UnorderedReduce (默认值),顺序未定义;而QtConcurrent::OrderedReduce 则确保归约操作按照原始序列的顺序进行。

其他 API 功能

使用迭代器代替序列

上述每个函数都有一个变体,该变体接受迭代器范围而非序列。其使用方式与序列变体相同:

QList<QImage> images = ...;

QFuture<QImage> thumbnails = QtConcurrent::mapped(images.constBegin(), images.constEnd(), scaled);

// Map in-place only works on non-const iterators.
QFuture<void> future = QtConcurrent::map(images.begin(), images.end(), scale);

QFuture<QImage> collage = QtConcurrent::mappedReduced(images.constBegin(), images.constEnd(), scaled, addToCollage);

阻塞变体

上述每个函数都有一个阻塞变体,该变体返回最终结果,而不是QFuture 。其使用方法与异步变体相同。

QList<QImage> images = ...;

// Each call blocks until the entire operation is finished.
QList<QImage> future = QtConcurrent::blockingMapped(images, scaled);

QtConcurrent::blockingMap(images, scale);

QImage collage = QtConcurrent::blockingMappedReduced(images, scaled, addToCollage);

请注意,上述结果类型并非QFuture 对象,而是真实的结果类型(在此情况下,分别为QList<QImage>和QImage)。

使用成员函数

QtConcurrent::map()、QtConcurrent::mapped() 和 QtConcurrent::mappedReduced() 接受成员函数指针。成员函数的类类型必须与序列中存储的类型匹配:

// 压缩 QStringList 中的所有字符串。
QStringList strings=...;
QFuture<void>squeezedStrings=QtConcurrent::map(strings, &QString::squeeze);

// 交换图像列表中所有像素的 RGB 值。
QList<QImage>images=...;
QFuture<QImage>bgrImages=QtConcurrent::mapped(images,
    static_cast<QImage(QImage::*)()const &>(&QImage::rgbSwapped));

// 创建一个集合,包含列表中所有字符串的长度。
QStringList strings=...;
QFuture<QSet<int>>单词长度=QtConcurrent::mappedReduced(strings, &QString::length,
                                                             qOverload<const int&>(&QSet<int>::insert));

请注意qOverload 的用法。这是为了消除方法的歧义,因为这些方法有多个重载。

另请注意,在使用 QtConcurrent::mappedReduced() 时,可以自由混合使用普通函数和成员函数:

// 可以在 QtConcurrent::mappedReduced() 中混合使用普通函数和成员函数。

// 计算字符串列表的平均长度。
extern voidcomputeAverage(int &average, intlength);
QStringList strings=...;
QFuture<int>averageWordLength=QtConcurrent::mappedReduced(strings, &QString::length,computeAverage);

// 创建一个集合,记录列表中所有图像的颜色分布。
extern intcolorDistribution(const QImage &string);
QList<QImage>images=...;
QFuture<QSet<int>>总颜色分布=QtConcurrent::mappedReduced(images,colorDistribution,
                                                                        qOverload<const int&>(&QSet<int>::insert));

使用函数对象

QtConcurrent::map()、QtConcurrent::mapped() 和 QtConcurrent::mappedReduced() 接受函数对象作为映射函数。这些函数对象可用于在函数调用中添加状态:

struct Scaled
{
    Scaled(int size)
    : m_size(size) { }

    typedef QImage result_type;

    QImage operator()(const QImage &image)
    {
        return image.scaled(m_size, m_size);
    }

    int m_size;
};

QList<QImage> images = ...;
QFuture<QImage> thumbnails = QtConcurrent::mapped(images, Scaled(100));

reduce 函数同样支持函数对象:

struct ImageTransform
{
    void operator()(QImage &result, const QImage &value);
};

QFuture<QImage> thumbNails =
        QtConcurrent::mappedReduced(images, Scaled(100), ImageTransform(),
                                    QtConcurrent::SequentialReduce);

使用 lambda 表达式

QtConcurrent::map()、QtConcurrent::mapped() 和 QtConcurrent::mappedReduced() 接受用于 map 和 reduce 函数的 lambda 表达式:

QList<int> vector { 1, 2, 3, 4 };
QtConcurrent::blockingMap(vector, [](int &x) { x *= 2; });

int size = 100;
QList<QImage> images = ...;

QList<QImage> thumbnails = QtConcurrent::mapped(images,
        [&size](const QImage &image) {
            return image.scaled(size, size);
        }
    ).results();

在使用 QtConcurrent::mappedReduced() 或 QtConcurrent::blockingMappedReduced() 时,您可以自由混合使用普通函数、成员函数和 lambda 表达式。

QList<QImage> collage = QtConcurrent::mappedReduced(images,
        [&size](const QImage &image) {
            return image.scaled(size, size);
        },
        addToCollage
   ).results();

您还可以将 lambda 作为 reduce 对象传递:

QList<QImage> collage = QtConcurrent::mappedReduced(images,
        [&size](const QImage &image) {
            return image.scaled(size, size);
        },
        [](QImage &result, const QImage &value) {
            // do some transformation
        }
   ).results();

包装接受多个参数的函数

若要使用接受多个参数的映射函数,可以使用 lambda 函数或 `std::bind() ` 将其转换为接受单个参数的函数。

以下以 QImage::scaledToWidth() 为例:

QImage QImage::scaledToWidth(int width, Qt::TransformationMode) const;

scaledToWidth 接受三个参数(包括“this”指针),无法直接与 QtConcurrent::mapped() 配合使用,因为 QtConcurrent::mapped() 期望接收一个只接受一个参数的函数。 若要将 QImage::scaledToWidth() 与 QtConcurrent::mapped() 结合使用,我们必须为宽度和 变换模式分别提供具体数值:

QList<QImage> images = ...;
std::function<QImage(const QImage &)> scale = [](const QImage &img) {
    return img.scaledToWidth(100, Qt::SmoothTransformation);
};
QFuture<QImage> thumbnails = QtConcurrent::mapped(images, scale);

© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.