当使用可变的lambda对std :: generate_n进行并行执行时,该可变lambda的捕获中包含初始化程序,那么并行访问初始化值是线程安全的吗?
[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
线程安全吗?
答案 0 :(得分:3)
首先,让我们看一下generate_n的签名:
template< class ExecutionPolicy, class ForwardIt , class Size, class Generator >
ForwardIt generate_n(ExecutionPolicy&& policy, ForwardIt first, Size count, Generator g);
重要的是,最后一个参数(即您的lambda)必须按值传递。另外,您也不知道在实现中如何在内部传递它,因此可能会有一些lambda副本,并且每个副本都有自己的计数器。我想这不是故意的。
有几种在实例之间共享计数器的选项:
在lambda上使用std :: ref:
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,因为您需要自己进行同步。
答案 1 :(得分:1)
访问捕获的
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
中的函数对象的副本甚至应该无法编译。因此,第一个示例显然是可取的,甚至可能是唯一可行的示例。