Disons que j’ai
std::tuple my_tuple{x0, x1, x2};
où T0
, T1
et T2
sont des types de valeur (c’est-à-dire qu’aucun repliement n’est possible) .
Est-il prudent d’accéder aux éléments de my_tuple
et de les muter simultanément à partir de plusieurs threads à l’aide de std::get
, tant que chaque thread accède à un élément différent?
Exemple:
template void process(T& x) { /* mutate `x` */ } // ... std::thread{[&]{ process(std::get(my_tuple)); }}.detach(); std::thread{[&]{ process(std::get(my_tuple)); }}.detach(); std::thread{[&]{ process(std::get(my_tuple)); }}.detach();
Instinctivement, je dirais que c’est sans danger, car my_tuple
peut être considéré comme struct { T0 x0; T1 x1; T2 x2; };
struct { T0 x0; T1 x1; T2 x2; };
… mais est-ce garanti par la norme?
Puisque std::get
ne spécifie pas explicitement ses propriétés de course de données dans la spécification, nous revenons au comportement par défaut défini dans [res.on.data.races]. Plus précisément, les paragraphes 2 et 3 racontent l’histoire:
Une fonction de bibliothèque standard C ++ ne doit pas accéder directement ou indirectement à des objects (1.10) accessibles par des threads autres que le thread actuel, à moins que les objects ne soient accessibles directement ou indirectement via les arguments de la fonction, y compris
this
.La fonction de bibliothèque standard AC ++ ne doit pas modifier directement ou indirectement les objects (1.10) accessibles par des threads autres que le thread actuel, à moins que les objects ne soient accessibles directement ou indirectement via les arguments non
const
la fonction, y compristhis
.
Celles-ci ne protègent des courses de données que pour des utilisations qui ne sont pas le même object fourni par les arguments d’une fonction. Un paramètre de modèle n’est pas techniquement l’argument d’une fonction, il n’est donc pas qualifié.
Votre cas implique que plusieurs threads transmettent le même object à différents appels get
. Puisque vous transmettez un paramètre non const
, get
sera supposé modifier son argument de tuple
. Par conséquent, l’appel de get
sur le même object compte pour la modification de l’object à partir de plusieurs threads. Et donc, l’appeler peut légalement provoquer une course de données sur le tuple
.
Même si, techniquement, il s’agit simplement d’extraire un sous-object du tuple
et ne doit donc pas perturber l’object lui-même ni ses autres sous-objects. La norme ne le sait pas.
Cependant , si le paramètre était const
, alors get
ne serait pas considéré comme provoquant une course de données avec d’autres appels const
à get
. Celles-ci consisteraient simplement à afficher le même object à partir de plusieurs threads, ce qui est autorisé dans la bibliothèque standard. Cela provoquerait une course de données avec des utilisations non const
de get
ou avec d’autres utilisations non const
de l’object tuple
. Mais pas avec des utilisations const
.
Ainsi, vous pouvez les “accéder”, mais pas les ” modifier “.
La réponse courte est que cela dépend des types et de ce que le process
fait au lieu d’ get
. Par lui-même, get
récupère simplement l’adresse de l’object et le renvoie comme référence. Récupérer l’adresse consiste principalement à lire le contenu des entiers. Cela n’augmente pas les conditions de course. En gros, l’extrait de code dans votre question est thread-safe si et seulement si ce qui suit est thread-safe,
T1 t1; T2 t2; T3 t3; std::thread{[&]{process(t1);}}.detach(); std::thread{[&]{process(t2);}}.detach(); std::thread{[&]{process(t3);}}.detach();