Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 650
Vittorio Romeo Avatar asked Nov 28 '16 13:11

Vittorio Romeo


People also ask

Is std :: function thread safe?

Calling the function it points to is as thread-safe as the code being called; no more, and no less. And even if you make a copy in the first case, you still need protection, so long as there is a potential writer somewhere in the mix.

Is std :: pair a tuple?

A tuple is an object capable to hold a collection of elements where each element can be of a different type. The class template needs the header <tuple> . std::tuple is a generalization of std::pair. You can convert between tuples with two elements and pairs.

What is std :: tuple in C++?

(since C++11) Class template std::tuple is a fixed-size collection of heterogeneous values. It is a generalization of std::pair. If std::is_trivially_destructible<Ti>::value is true for every Ti in Types , the destructor of tuple is trivial.

Are tuples immutable C++?

Tuples are immutable. Lists are mutable. Tuples can contain different data types.


1 Answers

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, including this.

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.

like image 141
Nicol Bolas Avatar answered Sep 20 '22 14:09

Nicol Bolas