QtConcurrent多线程 - map、mapped和mappedReduced

Qt中的 Concurrent 模块为我们提供了在一个序列容器类型针对每一个元素的多线程并行运算, 比如 QListQVector

  • QtConcurrent::map() :对序列的每一项元素都应用一个函数,并将运算结果替换原来的元素。
  • QtConcurrent::mapped() :功能类似 map() 函数,它会返回一个新容器存储函数处理后的结果。
  • QtConcurrent::mappedReduced() :类似于 mapped() ,他会将这个返回的结果序列,经过另一个函数处理为一个单个的值。

1. map 和 mapped

(1) QtConcurrent::map和QtConcurrent::mapped的基本形式如下:

QFuture<void> map(Sequence &sequence, MapFunctor map)

  • 参数 sequence :表示序列容器(如QList、QVector等)
  • 参数 map :表示函数。函数的形式必须符合 U function(T &t); 形式。
    类型 UT 可以为任何类型, 但是 T 的类型必须与容器中的类型保持一致。 返回值 U QtConcurrent::map 函数中并没有用到。
  • 返回值 QFuture<void> 。 关于 QFuture 这里就不过多介绍了,了解更多可以参考上一篇文章 QtConcurrent多线程 - run()与QFuture

下面是一个简单的使用示例:

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

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

该示例中,多线程对 QList<QImage> 容器中的每一个 QImage 缩放到 100 * 100 的尺寸。结果会覆盖掉原容器中的元素。

(2) QtConcurrent::mapped 函数跟 QtConcurrent::map 函数类似,不同之处在于:
  1. 传递的函数形式不同。它的形式为, U function(const T &t); 一个常引用做为参数,表示容器中的元素不可修改。返回值 U 存入整个 QtConcurrent::mapped 计算后的新容器中返回。
  2. 函数 QtConcurrent::mapped 的返回值为 QFuture<U> QFuture 除了可以表示单个类型也可以一组序列容器类型,跟单个的类似, 可以使用 results 函数返回运算后的结果。

下面是使用 QtConcurrent::mapped 的例子

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

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

这里同样也是对列表中的 QImage 进行缩放到 100 * 100 大小,但是容器本身的内容没有改变。
使用如下代码获取运算结果:

funcFunture2.waitForFinished();     // 等待运算处理完成
thumbnails.results();               // 获取运算结果

2. mappedReduced

mappedReduced() 类似于 QtConcurrent::mapped() , 但是它不是返回一个带有新结果的序列,而是使用reduce函数将结果组合成一个单独的值。

它的函数基本形式如下:

template <typename Sequence, typename MapFunctor, typename ReduceFunctor>
QFuture<typename QtPrivate::ReduceResultType<ReduceFunctor>::ResultType> 
mappedReduced(const Sequence &sequence,
              MapFunctor map,
              ReduceFunctor reduce,
              ReduceOptions options = ReduceOptions(UnorderedReduce | SequentialReduce))
  • 参数 sequence :表示序列容器。
  • 参数 map :同 mapped 函数, 形式为 U function(const T &t); 的函数。
  • 参数 reduce :处理返回后的结果序列的函数。 形式为 V function(T &result, const U &intermediate)T 为最终结果类型, Umap 函数的返回类型,V mappedReduced 并没有使用此返回值。
  • 参数 optionsreduce 函数的执行顺序, 默认为任意顺序。设置为 OrderedReduce表示按照原始序列的顺序执行。 而 SequentialReduce 则表示同一时刻, 只有一个线程在执行 reduce 函数,设置为此标志时, 可以不用对 reduce 函数进行加锁操作。
  • 返回值: 返回 QFuture<T> , 这个 T 类型就是 reduce 中的第一个参数。

下面是一个示例:

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

这里 images 中的每一个元素执行完 scaled 函数后的序列, 序列中的每一个元素再执行函数 addToCollage ,最后将计算结构返回。


3. 其他扩展

(1) 使用迭代器作为序列容器的输入范围。
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);
(2) 阻塞等待形式

map 、mapped和mappedReduced为非阻塞形式的多线程执行,可以使用 QFutureQFutureWatcher 作异步通知。但是Qt同样也提供了阻塞版本的函数:

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 ,而是真实的结果类型。 (在这个示例中,返回 QImageQList<QImage>

(3) 使用类的成员函数
// 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);
(4) 仿函数
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<QImage>(images,
                                    Scaled(100),
                                    ImageTransform(),
                                    QtConcurrent::SequentialReduce);
(5) lambda或使用std::bind,处理多参数函数

比如这个函数

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

可以借助 lambda 表达式处理参数

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);
不会飞的纸飞机
扫一扫二维码,了解我的更多动态。

下一篇文章:MySQL数据库学习笔记(1)- 数据库基本概念