c++ - threads - ¿Se garantiza que el uso de `std:: get<I>` en un `std:: tuple` sea seguro para subprocesos para diferentes valores de` I`?
threads c++ linux (2)
Digamos que tengo
std::tuple<T0, T1, T2> my_tuple{x0, x1, x2};
donde T0
, T1
y T2
son tipos de valor (es decir, no es posible crear un alias) .
¿Es seguro acceder a los elementos de my_tuple
y my_tuple
simultáneamente desde múltiples subprocesos usando std::get
, siempre que cada subproceso acceda a un elemento diferente?
Ejemplo:
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();
Instintivamente, diría que es seguro, ya que my_tuple
se puede considerar como struct { T0 x0; T1 x1; T2 x2; };
struct { T0 x0; T1 x1; T2 x2; };
... pero ¿está garantizado por la norma?
Como std::get
no tiene declaraciones explícitas en la especificación sobre sus propiedades de carrera de datos, recurrimos al comportamiento predeterminado definido en [res.on.data.races]. Específicamente, los párrafos 2 y 3 cuentan la historia:
Una función de biblioteca estándar de C ++ no accederá directa o indirectamente a objetos (1.10) a los que se puede acceder mediante subprocesos distintos del subproceso actual, a menos que se acceda a los objetos directa o indirectamente a través de los argumentos de la función, incluido
this
.La función de biblioteca estándar de AC ++ no modificará directa o indirectamente los objetos (1.10) accesibles por subprocesos distintos del subproceso actual a menos que se acceda a los objetos directa o indirectamente a través de los argumentos no
const
la función, incluidothis
.
Estos proporcionan protección contra las carreras de datos solo para usos que no son el mismo objeto proporcionado por los argumentos de una función. Un parámetro de plantilla no es técnicamente los argumentos de una función, por lo que no califica.
Su caso involucra múltiples hilos que pasan el mismo objeto a diferentes llamadas de get
. Como está pasando un parámetro no const
, get
asumirá que get
modifica su argumento de tuple
. Por lo tanto, las llamadas a get
en el mismo objeto cuentan como modificación del objeto desde varios subprocesos. Y por lo tanto, llamarlo puede provocar legalmente una carrera de datos en la tuple
.
Aunque, técnicamente hablando, solo se está extrayendo un subobjeto de la tuple
y, por lo tanto, no debe perturbar el objeto en sí o sus otros subobjetos. La norma no lo sabe.
Sin embargo , si el parámetro fuera const
, entonces get
no se consideraría para provocar una carrera de datos con otras const
para get
. Estos simplemente estarían viendo el mismo objeto desde múltiples hilos, lo que está permitido en la biblioteca estándar. Provocaría una carrera de datos con usos no const
de get
o con otros usos no const
del objeto tuple
. Pero no con const
usos de la misma.
Así que puedes "acceder" a ellos, pero no " modificarlos ".
La respuesta corta es que depende de los tipos y qué hace el process
lugar de get
. Por sí mismo, simplemente recupere la dirección del objeto y devuélvalo como referencia. La recuperación de la dirección consiste principalmente en leer el contenido de los números enteros. No levanta condiciones de carrera. En términos generales, el fragmento de código en su pregunta es seguro para subprocesos si y solo si el siguiente es seguro para subprocesos,
T1 t1;
T2 t2;
T3 t3;
std::thread{[&]{process(t1);}}.detach();
std::thread{[&]{process(t2);}}.detach();
std::thread{[&]{process(t3);}}.detach();