了解 tbb::parallel_reduce 的并发

Understanding concurrency of tbb::parallel_reduce

我将 tbb::parallel_reduce(range,body) 的命令形式用作 documented here。文档说:

A parallel_reduce uses the splitting constructor to make one or more copies of the body for each thread. It may copy a body while the body’s operator()or method join runs concurrently. You are responsible for ensuring the safety of such concurrency.

所以我明白 Body::Body( Body&, split ) 可以 运行 和 Body::operator()Body::join( Body& rhs ) 一样。

我的问题是

1)Body::operator()Body::join( Body& rhs )可以同时运行吗?

2) Body::join( Body& rhs ) 可以安全地修改 rhs 吗?

W.r.t。 1),我相信 join 只能在 operator() 完成后调用。此外,查看英特尔文档中的示例,很明显他们可以修改相同的数据而不会出现数据竞争问题:

struct Sum {
    float value;
    ...
    void operator()( const blocked_range<float*>& r ) {
        value = ...;
    }
    void join( Sum& rhs ) {value += rhs.value;}
};               

关于并发调用方法所涉及的实例的文档不够清楚。但它提示:

In typical usage, the safety requires no extra effort.

同实例没有并发。因此,在串行代码中拆分或连接两个实例是安全的。 operator() 也不能在同一实例上同时调用。所以文档必须真正说:

It may copy a body while the body’s operator() or method join runs concurrently on different instances.

顺便说一句,引用

A parallel_reduce recursively splits the range into subranges to the point such that is_divisible() is false for each subrange.`

仅对simple_partitioner有效,对于其他partitioner,可以在达到is_divisible() == false

之前停止分裂

tbb::parallel_reduce 使用的 Body object 的生命周期是:

  1. 它由拆分构造函数创建为另一个 Body object 的 "right" 端,后者又被更新为仅处理 "left" 端。
  2. operator()应用于一个或多个后续范围。
  3. 它可以用作拆分新主体构造函数的参数(是的,不止一次),并根据上面的#1 进行更新。
  4. 它的方法 join() 在 #3 中为每个 object 拆分被调用。
  5. 它以 rhs 的形式加入到它从中分离出来的 body object。
  6. 加入后销毁

在上述操作中,#3可以运行并发到#2和#4;其余的都是顺序的,即使不一定由单个线程调用。换句话说,虽然 body 为某些子范围累积部分缩减或加入另一个 body 的结果,但可以通过拆分新主体的构造函数来更新它。这是可能需要保护的地方,但通常这些调用会访问 body.

的不同部分

那么,您的问题的答案是:

1) Can Body::operator() and Body::join( Body& rhs ) be run concurrently?

没有。 join() 仅在对 operator() 的所有调用之后调用,对于 thisrhs

2) Can Body::join( Body& rhs ) safely modify rhs?

是的,可以。但是除了可能在连接期间移动其内容并相应地更新状态之外,修改 rhs 没有什么意义,因为它会在 join() 调用后立即被销毁。