Concurrent Map and Map-Reduce

QtConcurrent::map(), QtConcurrent::mapped() 和 QtConcurrent::mappedReduced() 函数对容器(如 QListQVector)中的每一元素并行计算. 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).

Concurrent Map

QtConcurrent::mapped() 的参数是一个序列容器和一个映射函数. 这个函数为容器中的每个元素调用映射函数, 返回一个包含映射函数返回值的新序列.

映射函数形式必须如下:


  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);

map的计算结果可以通过 QFuture获取. 在应用程序中如何使用QFuture详见 QFutureQFutureWatcher.

如果你想修改容器序列, 使用 QtConcurrent::map(). map函数必须如下形式:


  U function(T &t);

注意: 上述函数的返回值和返回类型未使用.

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返回任何值. 然而, 你仍然可以使用 QFutureQFutureWatcher 监控 map 状态.

Concurrent Map-Reduce

QtConcurrent::mappedReduced() 与 QtConcurrent::mapped()类似, 但是使用新的类型结果代替容器, 结果是使用reduce函数组合成一个简单值.

reduce函数必须如下形式:


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

T类型是最终结果, U是函数的返回类型. 注意: reduce函数的返回值和返回类型未使用.

如下方式调用 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);

对于map函数返回的每个结果, reduce函数被调用一次, 并将中间值合并到结果变量中. QtConcurrent::mappedReduced() 保证每次只有一个线程调用reduce, 因此不需要使用互斥锁锁定结果变量. QtConcurrent::ReduceOptions 枚举类型指明调用reduce函数顺序. 如果使用 QtConcurrent::UnorderedReduce (默认), 调用顺序未定义, QtConcurrent::OrderedReduce 表明以容器的元素顺序调用reduce函数.

Additional API Features

使用迭代器代替容器

上述的每个函数都有一个对应的迭代器函数, 使用迭代器范围代替容器. 使用它们的方式与容器参数一样:


  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(), and QtConcurrent::mappedReduced() 接受成员函数的指针. 这个成员函数的类型必须与容器元素类型一致:


  // squeeze all strings in a QStringList
  QStringList strings = ...;
  QFuture<void> squeezedStrings = QtConcurrent::map(strings, &QString::squeeze);

  // swap the rgb values of all pixels on a list of images
  QList<QImage> images = ...;
  QFuture<QImage> bgrImages = QtConcurrent::mapped(images, &QImage::rgbSwapped);

  // create a set of the lengths of all strings in a list
  QStringList strings = ...;
  QFuture<QSet<int> > wordLengths = QtConcurrent::mappedReduced(string, &QString::length, &QSet<int>::insert);

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


  // can mix normal functions and member functions with QtConcurrent::mappedReduced()

  // compute the average length of a list of strings
  extern void computeAverage(int &average, int length);
  QStringList strings = ...;
  QFuture<int> averageWordLength = QtConcurrent::mappedReduced(strings, &QString::length, computeAverage);

  // create a set of the color distribution of all images in a list
  extern int colorDistribution(const QImage &string);
  QList<QImage> images = ...;
  QFuture<QSet<int> > totalColorDistribution = QtConcurrent::mappedReduced(images, colorDistribution, QSet<int>::insert);

使用函数对象

QtConcurrent::map(), QtConcurrent::mapped(), QtConcurrent::mappedReduced() 接受函数对象作为映射函数, 这些函数对象用于为函数调用增加状态记录. result_type 必须通过typedef定义为函数的返回类型:


  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));

使用多个参数的包装函数

如果你想使用一个包含多个参数的映射函数, 你可以使用lambda函数或std::bind() 将其转换为包含一个参数的函数.

例如, 我们想要使用 QImage::scaledToWidth()作为映射函数:


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

scaledToWidth()函数接受三个参数 (包括 "this" 指针), 不能直接作为QtConcurrent::mapped()的映射函数, 因为 QtConcurrent::mapped() 只接受一个参数的函数. 想要使用 QImage::scaledToWidth() 作为 QtConcurrent::mapped()映射函数, 我们必须为参数 widthtransformation mode提供默认值:


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