Dans la documentation de std::memory_order
sur cppreference.com, il y a un exemple de commande détendue:
Commande détendue
Les opérations atomiques marquées
memory_order_relaxed
ne sont pas des opérations de synchronisation; ils n'imposent pas d'ordre entre les accès simultanés à la mémoire. Ils garantissent uniquement la cohérence de l'ordre d'atomicité et de modification.Par exemple, avec x et y initialement zéro,
// Thread 1: r1 = y.load(std::memory_order_relaxed); // A x.store(r1, std::memory_order_relaxed); // B // Thread 2: r2 = x.load(std::memory_order_relaxed); // C y.store(42, std::memory_order_relaxed); // D
est autorisé à produire r1 == r2 == 42 car, bien que A soit séquencé avant B dans le thread 1 et que C soit séquencé avant D dans le thread 2, rien n'empêche D d'apparaître avant A dans l'ordre de modification de y, et B de apparaissant avant C dans l'ordre de modification de x. L'effet secondaire de D sur y pourrait être visible pour la charge A dans le fil 1 tandis que l'effet secondaire de B sur x pourrait être visible pour la charge C dans le fil 2. En particulier, cela peut se produire si D est terminé avant C dans thread 2, soit en raison de la réorganisation du compilateur, soit au moment de l'exécution.
il dit "C est séquencé avant D dans le thread 2".
Selon la définition de séquencé avant, qui peut être trouvée dans l' ordre d'évaluation , si A est séquencé avant B, alors l'évaluation de A sera terminée avant que l'évaluation de B ne commence. Puisque C est séquencé avant D dans le thread 2, C doit être terminé avant le début de D, par conséquent la partie condition de la dernière phrase de l'instantané ne sera jamais satisfaite.
la source
Réponses:
Je crois que la référence est juste. Je pense que cela se résume à la règle "comme si" [intro.execution] / 1 . Les compilateurs sont uniquement tenus de reproduire le comportement observable du programme décrit par votre code. Une relation séquentielle avant n'est établie qu'entre les évaluations du point de vue du fil dans lequel ces évaluations sont effectuées [intro.execution] / 15 . Cela signifie que lorsque deux évaluations séquencées l'une après l'autre apparaissent quelque part dans un thread, le code qui s'exécute réellement dans ce thread doit se comporter comme si tout ce que faisait la première évaluation affectait effectivement tout ce que faisait la deuxième évaluation. Par exemple
doit imprimer 42. Cependant, le compilateur n'a pas réellement à stocker la valeur 42 dans un objet
x
avant de relire la valeur de cet objet pour l'imprimer. Il peut tout aussi bien se rappeler que la dernière valeur à stockerx
était 42 et ensuite simplement imprimer la valeur 42 directement avant d'effectuer un stockage réel de la valeur 42 dansx
. En fait, s'ilx
s'agit d'une variable locale, il peut tout aussi bien suivre la valeur que cette variable a été affectée en dernier à n'importe quel moment et ne jamais même créer un objet ou stocker réellement la valeur 42. Il n'y a aucun moyen pour le thread de faire la différence. Le comportement sera toujours comme s'il y avait une variable et comme si la valeur 42 était réellement stockée dans un objetx
avanten cours de chargement à partir de cet objet. Mais cela ne signifie pas que le code machine généré doit réellement stocker et charger n'importe quoi n'importe où. Tout ce qui est requis, c'est que le comportement observable du code machine généré est indiscernable de ce que serait le comportement si toutes ces choses devaient réellement se produire.Si nous regardons
alors oui, C est séquencé avant D. Mais vu de ce fil isolément, rien de ce que C fait affecte le résultat de D. Et rien de D ne changerait le résultat de C. La seule façon dont l'un pourrait affecter l'autre serait comme une conséquence indirecte de quelque chose qui se passe dans un autre thread. Cependant, en précisant
std::memory_order_relaxed
, vous avez explicitement déclaréque l'ordre dans lequel la charge et le magasin sont observés par un autre thread n'est pas pertinent. Puisqu'aucun autre thread ne peut observer la charge et stocker dans un ordre particulier, il n'y a rien d'autre qu'un thread puisse faire pour que C et D s'influencent de manière cohérente. Ainsi, l'ordre dans lequel le chargement et le stockage sont réellement effectués n'est pas pertinent. Ainsi, le compilateur est libre de les réorganiser. Et, comme mentionné dans l'explication sous cet exemple, si le stockage de D est effectué avant la charge de C, alors r1 == r2 == 42 peut en effet se produire…la source
Il est parfois possible qu'une action soit ordonnée par rapport à deux autres séquences d'actions, sans impliquer aucun ordre relatif des actions dans ces séquences l'une par rapport à l'autre.
Supposons, par exemple, que l'on ait les trois événements suivants:
et la lecture de p2 est ordonnée indépendamment après l'écriture de p1 et avant l'écriture de p3, mais il n'y a pas d'ordre particulier dans lequel participent à la fois p1 et p3. En fonction de ce qui est fait avec p2, il peut être impossible pour un compilateur de reporter p1 au-delà de p3 tout en réalisant la sémantique requise avec p2. Supposons cependant que le compilateur savait que le code ci-dessus faisait partie d'une séquence plus large:
Dans ce cas, il pourrait déterminer qu'il pourrait réorganiser le magasin en p1 après le code ci-dessus et le consolider avec le magasin suivant, résultant ainsi en un code qui écrit p3 sans écrire p1 en premier:
Bien qu'il puisse sembler que les dépendances de données entraîneraient un comportement transitoire de certaines parties des relations de séquençage, un compilateur peut identifier des situations où les dépendances de données apparentes n'existent pas et n'auraient donc pas les effets transitifs attendus.
la source
S'il y a deux instructions, le compilateur générera du code dans un ordre séquentiel afin que le code de la première soit placé avant la seconde. Mais les processeurs disposent en interne de pipelines et sont capables d'exécuter des opérations d'assemblage en parallèle. L'instruction C est une instruction de chargement. Pendant que la mémoire est récupérée, le pipeline traitera les quelques instructions suivantes et étant donné qu'elles ne dépendent pas de l'instruction de chargement, elles pourraient finir par être exécutées avant la fin de C (par exemple, les données pour D étaient dans le cache, C dans la mémoire principale).
Si l'utilisateur a vraiment besoin que les deux instructions soient exécutées séquentiellement, des opérations de classement de mémoire plus strictes peuvent être utilisées. En général, les utilisateurs s'en moquent tant que le programme est logiquement correct.
la source
Tout ce que vous pensez est également valable. La norme ne dit pas ce qui s'exécute séquentiellement, ce qui ne fonctionne pas et comment cela peut être mélangé .
C'est à vous, et à chaque programmeur, de créer une sémantique cohérente en plus de ce gâchis, un travail digne de plusieurs doctorats.
la source