在 `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};
其中 T0
、T1
和 T2
是值类型 (即不可能使用别名).
访问 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();
假设我有
std::tuple<T0, T1, T2> my_tuple{x0, x1, x2};
其中 T0
、T1
和 T2
是值类型 (即不可能使用别名).
访问 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, includingthis
.
这些仅针对与函数参数提供的对象不同的用途提供数据竞争保护。模板参数在技术上不是函数的参数,因此它不合格。
您的案例涉及多个线程将同一对象传递给不同的 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();