问题
Let's say I have
std::tuple<T0, T1, T2> my_tuple{x0, x1, x2};
where T0
, T1
and T2
are value types (i.e. no aliasing is possible).
Is it safe to access my_tuple
's elements and mutate them concurrently from multiple threads using std::get
, as long as every thread accesses a different element?
Example:
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();
Instinctively I would say it is safe, as my_tuple
can be thought of as struct { T0 x0; T1 x1; T2 x2; };
... but is it guaranteed by the standard?
回答1:
Since std::get
has no explicit statements in the specification about its data race properties, we fall back to the default behavior defined in [res.on.data.races]. Specifically, paragraphs 2 and 3 tell the story:
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
.
These provide protection from data races only for uses that are not the same object provided by a function's arguments. A template parameter is not technically a function's arguments, so it doesn't qualify.
Your case involves multiple threads passing the same object to different get
calls. Since you are passing a non-const
parameter, get
will be assumed to be modifying its tuple
argument. Therefore, calling get
on the same object counts as modifying the object from multiple threads. And therefore, calling it can legally provoke a data race on the tuple
.
Even though, technically speaking, it's just extracting a subobject from the tuple
and therefore should not disturb the object itself or its other subobjects. The standard does not know this.
However, if the parameter were const
, then get
would not be considered to provoke a data race with other const
calls to get
. These would simply be viewing the same object from multiple threads, which is allowed in the standard library. It would provoke a data race with non-const
uses of get
or with other non-const
uses of the tuple
object. But not with const
uses of it.
So you can "access" them, but not "modify" them.
回答2:
The short answer is that it depends on the types and what does process
do instead of get
. By itself, get
merely retrieve the address of the object and return it as a reference. Retrieving the address is mostly just reading the contents of integers. It does not raise race conditions. Roughly speaking, the code snippet in your question is thread-safe if and only if the following is thread-safe,
T1 t1;
T2 t2;
T3 t3;
std::thread{[&]{process(t1);}}.detach();
std::thread{[&]{process(t2);}}.detach();
std::thread{[&]{process(t3);}}.detach();
来源:https://stackoverflow.com/questions/40845699/is-using-stdgeti-on-a-stdtuple-guaranteed-to-be-thread-safe-for-differ