在 `std::tuple` 上使用 `std::get<I>` 是否保证对于不同的 `I` 值是线程安全的?

Is using `std::get<I>` on a `std::tuple` guaranteed to be thread-safe for different values of `I`?

假设我有

std::tuple<T0, T1, T2> my_tuple{x0, x1, x2};

其中 T0T1T2 是值类型 (即不可能使用别名).

访问 my_tuple 的元素并使用 std::get 从多个线程同时改变它们是否安全,只要每个线程访问不同的元素?

示例:

template <typename T>
void process(T& x) { /* mutate `x` */ }

// ...

std::thread{[&]{ process(std::get<0>(my_tuple)); }}.detach();
std::thread{[&]{ process(std::get<1>(my_tuple)); }}.detach();
std::thread{[&]{ process(std::get<2>(my_tuple)); }}.detach();

本能地我会说它是安全的,因为 my_tuple 可以被认为是 struct { T0 x0; T1 x1; T2 x2; };...但是它是由标准保证的吗?

由于 std::get 在规范中没有关于其数据竞争属性的明确声明,我们回退到 [res.on.data.races] 中定义的默认行为。具体来说,第 2 段和第 3 段讲述了这个故事:

A C++ standard library function shall not directly or indirectly access objects (1.10) accessible by threads other than the current thread unless the objects are accessed directly or indirectly via the function’s arguments, including this.

A C ++ standard library function shall not directly or indirectly modify objects (1.10) accessible by threads other than the current thread unless the objects are accessed directly or indirectly via the function’s non-const arguments, including this.

这些仅针对与函数参数提供的对象不同的用途提供数据竞争保护。模板参数在技术上不是函数的参数,因此它不合格。

您的案例涉及多个线程将同一对象传递给不同的 get 调用。由于您传递的是非 const 参数,因此 get 将被假定为正在修改其 tuple 参数。因此,对同一个对象调用 get 算作从多个线程修改对象。因此,调用它可以合法地在 tuple.

上引发数据竞争

尽管从技术上讲,它只是从 tuple 中提取一个子对象,因此不应干扰该对象本身或其其他子对象。标准不知道这个。

但是,如果参数是const,那么get不会被认为与其他const调用引发数据竞争至 get。这些只是从多个线程查看同一个对象,这在标准库中是允许的。它将引发 get 的非 const 使用或 tuple 对象的其他非 const 使用的数据竞争。但不适用于 const 用途。

所以你可以"access"他们,但不能“修改”他们。

简短的回答是,它取决于类型以及 process 而不是 get 的作用。就其本身而言,get 仅检索对象的地址并将其 return 作为参考。检索地址主要是读取整数的内容。它不会提高竞争条件。粗略地说,您问题中的代码片段是线程安全的,当且仅当以下内容是线程安全的,

T1 t1;
T2 t2;
T3 t3;

std::thread{[&]{process(t1);}}.detach();
std::thread{[&]{process(t2);}}.detach();
std::thread{[&]{process(t3);}}.detach();