当使用在其捕获中具有初始化器的可变lambda对std :: generate_n使用并行执行时,是否可以并行访问初始化值线程安全?
[MCVE]
#include<vector>
#include <algorithm>
#include <execution>
int main()
{
std::vector<int> v(1000);
std::generate_n(std::execution::par, v.data(), v.size(), [i = 0]() mutable { return i++; });
return 0;
}
是否可以访问捕获的i
线程安全?
是否可以访问捕获的
i
线程安全?
不。客户端代码负责确保不会发生数据争用。你能做的就是这个(从cppreference复制和定制)
int i = 0;
std::mutex m;
std::generate_n(std::execution::par, v.data(), v.size(), [&]() {
std::lock_guard<std::mutex> guard(m);
return i++; });
或者,如果你坚持lambda捕获与mutable
关键字:
std::generate_n(std::execution::par, v.data(), v.size(),
[i = 0, m = std::mutex()] () mutable {
std::lock_guard<std::mutex> guard(m);
return i++; });
请注意,正如@Eric在评论中指出的那样,@ DmitryGordon在他的回答中指出,std::generate_n
可能会复制函数对象。这是有问题的,因为每个复制的实例都有自己的计数器i
,该计数器独立于其他实例递增。另请注意,@ rubenvb指出std::generate_n
中的函数对象的副本甚至无法编译。因此,第一个例子显然是可取的,甚至可能是唯一可行的例子。
首先,让我们看一下generate_n的签名:
template< class ExecutionPolicy, class ForwardIt , class Size, class Generator >
ForwardIt generate_n(ExecutionPolicy&& policy, ForwardIt first, Size count, Generator g);
重要的是最后一个参数(它是你的lambda)是按值传递的。另外你不知道它是如何在实现中内部传递的,所以你的lambda可能有一些副本,每个副本都有它自己的计数器。我想这不是意图。
在实例之间共享计数器有几种选择:
const auto func = [i = std::atomic<int>()]() mutable -> int {
return i++; };
std::vector<int> v(1000);
std::generate_n(std::execution::par, v.data(), v.size(), std::ref(func));
std::atomic<int> i = 0;
std::vector<int> v(1000);
std::generate_n(std::execution::par, v.data(), v.size(), [&i]() -> int { return i++; });
请注意,在这两种情况下我都使用std :: atomic,因为您需要自己处理同步。