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

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

本文关键字:std 安全 线程 get tuple gt lt 是否      更新时间:2023-10-16

假设我有

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段讲述了这个故事:

C++ 标准库函数不得直接或间接访问由当前线程以外的线程访问的对象 (1.10),除非通过函数的参数直接或间接访问对象, 包括this.

C++ 标准库函数不得直接或间接修改由当前线程以外的线程访问的对象 (1.10),除非通过函数的非const参数(包括this)直接或间接访问对象。

这些仅针对与函数参数提供的同一对象不同的用途提供数据争用保护。从技术上讲,模板参数不是函数的参数,因此它不符合条件。

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

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

但是,如果参数const,则get不会被视为引发与其他constget调用的数据竞争。这些只是从多个线程查看同一对象,这在标准库中是允许的。这将引发一场数据竞赛,非const使用gettuple对象的其他非const用途。但不是const使用它。

因此,您可以"访问"它们,但不能">修改"它们。

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

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