J'ai écouté et lu plusieurs articles, discussions et questions sur le stackoverflow std::atomic
, et je voudrais être sûr d'avoir bien compris. Parce que je suis toujours un peu confus avec la visibilité des écritures de la ligne de cache en raison de retards possibles dans les protocoles de cohérence de cache MESI (ou dérivés), les tampons de stockage, les files d'attente invalides, etc.
J'ai lu que x86 a un modèle de mémoire plus fort, et que si une invalidation de cache est retardée, x86 peut annuler les opérations démarrées. Mais je ne m'intéresse maintenant qu'à ce que je devrais assumer en tant que programmeur C ++, indépendamment de la plateforme.
[T1: thread1 T2: thread2 V1: variable atomique partagée]
Je comprends que std :: atomic garantit que,
(1) Aucune course de données ne se produit sur une variable (grâce à l'accès exclusif à la ligne de cache).
(2) En fonction de l'ordre mémoire que nous utilisons, il garantit (avec des barrières) que la cohérence séquentielle se produit (avant une barrière, après une barrière ou les deux).
(3) Après une écriture atomique (V1) sur T1, une RMW atomique (V1) sur T2 sera cohérente (sa ligne de cache aura été mise à jour avec la valeur écrite sur T1).
Mais comme le mentionne l' amorce de cohérence du cache ,
L'implication de toutes ces choses est que, par défaut, les charges peuvent récupérer des données périmées (si une demande d'invalidation correspondante se trouvait dans la file d'attente d'invalidation)
Alors, est-ce que ce qui suit est correct?
(4) std::atomic
ne garantit PAS que T2 ne lira pas une valeur «périmée» sur une lecture atomique (V) après une écriture atomique (V) sur T1.
Questions si (4) a raison: si l'écriture atomique sur T1 invalide la ligne de cache quel que soit le retard, pourquoi T2 attend-il que l'invalidation soit effective quand une opération RMW atomique mais pas sur une lecture atomique?
Questions si (4) est faux: quand un thread peut-il lire une valeur "périmée" et "c'est visible" dans l'exécution, alors?
J'apprécie beaucoup vos réponses
Mise à jour 1
Il semble donc que j'avais tort sur (3). Imaginez l'entrelacement suivant, pour un V1 initial = 0:
T1: W(1)
T2: R(0) M(++) W(1)
Même si RMW de T2 est garanti de se produire entièrement après W (1) dans ce cas, il peut toujours lire une valeur «périmée» (j'avais tort). Selon cela, atomic ne garantit pas la cohérence complète du cache, seulement la cohérence séquentielle.
Mise à jour 2
(5) Imaginez maintenant cet exemple (x = y = 0 et sont atomiques):
T1: x = 1;
T2: y = 1;
T3: if (x==1 && y==0) print("msg");
selon ce dont nous avons parlé, voir le "msg" affiché à l'écran ne nous donnerait pas d'informations au-delà du fait que T2 a été exécuté après T1. Il se peut donc que l'une des exécutions suivantes se soit produite:
- T1 <T3 <T2
- T1 <T2 <T3 (où T3 voit x = 1 mais pas encore y = 1)
Est-ce correct?
(6) Si un thread peut toujours lire des valeurs «périmées», que se passerait-il si nous prenions le scénario typique de «publication» mais au lieu de signaler que certaines données sont prêtes, nous faisons exactement le contraire (supprimer les données)?
T1: delete gameObjectPtr; is_enabled.store(false, std::memory_order_release);
T2: while (is_enabled.load(std::memory_order_acquire)) gameObjectPtr->doSomething();
où T2 utiliserait toujours un ptr supprimé jusqu'à ce que is_enabled soit faux.
(7) De plus, le fait que les threads puissent lire des valeurs «périmées» signifie qu'un mutex ne peut pas être implémenté avec un seul droit atomique sans verrouillage? Cela nécessiterait un mécanisme de synchronisation entre les threads. Aurait-il besoin d'un atomique verrouillable?
Concernant (3) - cela dépend de l'ordre de la mémoire utilisée. Si les deux, le magasin et l'opération RMW utilisent
std::memory_order_seq_cst
, alors les deux opérations sont ordonnées d'une manière ou d'une autre - c'est-à-dire que le magasin se produit avant le RMW, ou l'inverse. Si le magasin est commandé avant le RMW, alors il est garanti que l'opération RMW "voit" la valeur qui a été stockée. Si le magasin est commandé après le RMW, il écraserait la valeur écrite par l'opération RMW.Si vous utilisez des ordres de mémoire plus détendus, les modifications seront toujours ordonnées d'une certaine manière (l'ordre de modification de la variable), mais vous n'avez aucune garantie si le RMW "voit" la valeur de l'opération de stockage - même si l'opération RMW est l'ordre après l'écriture dans l'ordre de modification de la variable.
Si vous souhaitez lire un autre article, je peux vous référer aux modèles de mémoire pour les programmeurs C / C ++ .
la source