L’utilisation de `std :: get ` sur un `std :: tuple` est-elle garantie pour les threads pour différentes valeurs de` I`?

Disons que j’ai

std::tuple my_tuple{x0, x1, x2}; 

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 compris this .

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();