了解 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 的生命周期是:
- 它由拆分构造函数创建为另一个 Body object 的 "right" 端,后者又被更新为仅处理 "left" 端。
- 其
operator()
应用于一个或多个后续范围。
- 它可以用作拆分新主体构造函数的参数(是的,不止一次),并根据上面的#1 进行更新。
- 它的方法
join()
在 #3 中为每个 object 拆分被调用。
- 它以
rhs
的形式加入到它从中分离出来的 body object。
- 加入后销毁
在上述操作中,#3可以运行并发到#2和#4;其余的都是顺序的,即使不一定由单个线程调用。换句话说,虽然 body 为某些子范围累积部分缩减或加入另一个 body 的结果,但可以通过拆分新主体的构造函数来更新它。这是可能需要保护的地方,但通常这些调用会访问 body.
的不同部分
那么,您的问题的答案是:
1) Can Body::operator()
and Body::join( Body& rhs )
be run concurrently?
没有。 join()
仅在对 operator()
的所有调用之后调用,对于 this
和 rhs
。
2) Can Body::join( Body& rhs )
safely modify rhs
?
是的,可以。但是除了可能在连接期间移动其内容并相应地更新状态之外,修改 rhs
没有什么意义,因为它会在 join()
调用后立即被销毁。
我将 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 的生命周期是:
- 它由拆分构造函数创建为另一个 Body object 的 "right" 端,后者又被更新为仅处理 "left" 端。
- 其
operator()
应用于一个或多个后续范围。 - 它可以用作拆分新主体构造函数的参数(是的,不止一次),并根据上面的#1 进行更新。
- 它的方法
join()
在 #3 中为每个 object 拆分被调用。 - 它以
rhs
的形式加入到它从中分离出来的 body object。 - 加入后销毁
在上述操作中,#3可以运行并发到#2和#4;其余的都是顺序的,即使不一定由单个线程调用。换句话说,虽然 body 为某些子范围累积部分缩减或加入另一个 body 的结果,但可以通过拆分新主体的构造函数来更新它。这是可能需要保护的地方,但通常这些调用会访问 body.
的不同部分那么,您的问题的答案是:
1) Can
Body::operator()
andBody::join( Body& rhs )
be run concurrently?
没有。 join()
仅在对 operator()
的所有调用之后调用,对于 this
和 rhs
。
2) Can
Body::join( Body& rhs )
safely modifyrhs
?
是的,可以。但是除了可能在连接期间移动其内容并相应地更新状态之外,修改 rhs
没有什么意义,因为它会在 join()
调用后立即被销毁。