Question

Quelques langages fournissent un modificateur de volatile qui est décrit comme la réalisation d'une « barrière de mémoire de lecture » avant la lecture de la mémoire qui sauvegarde une variable.

Une barrière de mémoire morte est communément décrit comme un moyen de faire en sorte que la CPU a exécuté le lit demandé avant la barrière avant d'effectuer une lecture demandée après la barrière. Cependant, en utilisant cette définition, il semblerait que pouvait encore lire une valeur périmée. En d'autres termes, l'exécution se lit dans un certain ordre ne semble pas signifier que la mémoire principale ou d'autres unités centrales doivent être consultées pour assurer que les valeurs suivantes lues reflètent effectivement la dernière dans le système au moment de la barrière de lecture ou d'écriture par la suite après la lire la barrière.

Alors, ne garantit volatile vraiment qu'une valeur mise à jour est lu ou juste (gasp!) Que les valeurs qui sont lues sont au moins aussi mise à jour que les lectures avant la barrière? Ou une autre interprétation? Quelles sont les implications pratiques de cette réponse?

Était-ce utile?

La solution

Il y a des obstacles et de lecture d'écriture des obstacles; acquérir des obstacles et libérer les obstacles. Et plus (io vs mémoire, etc).

Les barrières ne sont pas là pour contrôler « dernière » valeur ou « fraîcheur » des valeurs. Ils sont là pour contrôler l'ordre relatif des accès mémoire.

barrières écriture contrôler l'ordre des écritures. Parce que les écritures en mémoire sont lents (par rapport à la vitesse de la CPU), il y a généralement une file d'attente de requête d'écriture où les écritures sont affichés avant qu'ils ne se produisent « vraiment ». Bien qu'ils soient mis en attente dans l'ordre, tandis qu'à l'intérieur de la file d'attente les écritures peuvent être réorganisés. (Peut-être « file d'attente » n'est pas le meilleur nom ...) À moins d'utiliser des barrières d'écriture pour éviter la remise en ordre.

Lire les barrières de contrôle de l'ordre de lectures. En raison de l'exécution spéculative (CPU regarde vers l'avenir et les charges de la mémoire au début) et à cause de l'existence du tampon d'écriture (la CPU lire une valeur de la mémoire tampon d'écriture au lieu de la mémoire si elle est là - à savoir la CPU pense qu'il vient d'écrire X = 5, alors pourquoi lire en arrière, juste voir qu'il attend toujours devenir 5 dans le tampon d'écriture) lit peut se produire hors d'usage.

Ceci est vrai quel que soit ce que le compilateur essaie de faire par rapport à l'ordre du code généré. à-dire « volatile » en C ++ ne sera pas utile ici, car il dit que le compilateur de code de sortie pour relire la valeur de « mémoire », il ne dit pas la CPU comment / où lire de (c.-à « mémoire » est beaucoup de choses au niveau du CPU).

lecture / écriture des obstacles mis en place des blocs pour éviter les files d'attente dans réordonnancement de lecture / écriture (la lecture est généralement pas tellement d'une file d'attente, mais les effets sont les mêmes réordonnancement).

Quels types de blocs? -. Acquérir et / ou libérer des blocs

Acquire - par exemple, lire-acquire (x) ajoutera la lecture de x dans la lecture file d'attente et vider la file d'attente (pas vraiment vider la file d'attente, mais ajoutez un dicton marqueur ne pas modifier l'ordre quoi que ce soit avant cette lecture, qui est comme si la file d'attente a été purgée). Donc, plus tard (dans le code commande) lit peut être réorganisées, mais pas avant la lecture de x.

Release - par exemple écrire libération (x, 5) videra (ou marqueur) la file d'attente, puis ajouter la requête d'écriture à l'écriture file d'attente. Donc, plus tôt écrit ne deviendra pas réorganisés arriver après x = 5, mais notez que les écritures plus tard peuvent être réorganisés avant x = 5.

Notez que j'apparié la lecture et écrire avec acquisition avec la libération parce que ce qui est typique, mais différentes combinaisons sont possibles.

Acquire et Release sont considérés comme des « demi-barrières » ou « demi-clôtures » parce qu'ils arrêtent seulement le réordonnancement d'aller dans un sens.

Une barrière complète (ou une clôture complète) applique à la fois une et une libération acquisition - c.-à-pas réordonner.

En général pour la programmation lockfree ou C # ou Java 'volatile', ce que vous voulez / besoin est lecture acquérir et écriture libération.

soit

void threadA()
{
   foo->x = 10;
   foo->y = 11;
   foo->z = 12;
   write_release(foo->ready, true);
   bar = 13;
}
void threadB()
{
   w = some_global;
   ready = read_acquire(foo->ready);
   if (ready)
   {
      q = w * foo->x * foo->y * foo->z;
   }
   else
       calculate_pi();
}

Alors, tout d'abord, cela est une mauvaise façon de programmer les discussions. Serrures seraient plus en sécurité. Mais juste pour illustrer les obstacles ...

Après threadA () est fait par écrit foo, il a besoin d'écrire foo-> prêt LAST, vraiment la dernière, sinon d'autres threads peut voir foo-> prêts tôt et obtenir les mauvaises valeurs de x / y / z. Nous utilisons donc un write_release sur foo-> prêt, qui, comme mentionné ci-dessus, effectivement « » la file d'attente Bouffées d'écriture (x assurer, y, z sont engagés) ajoute alors le prêt = true demande à la file d'attente. Et puis ajoute la barre = 13 demande. Notez que puisque nous venons d'utiliser une barrière de sortie (pas plein) bar = 13 peut s'écrit avant prêt. Mais nous ne nous soucions pas! dire que nous partons du principe bar ne change pas les données partagées.

ThreadB () a besoin de savoir que lorsque nous disons « prêt », nous entendons vraiment prêt. Nous faisons donc un read_acquire(foo->ready). Cette lecture est ajouté à la file d'attente de lecture, alors la file d'attente est rincée. Notez que w = some_global peut également être encore dans la file d'attente. Alors foo-> prêt peut être lu avant some_global. Mais encore une fois, nous ne nous soucions pas, car il ne fait pas partie des données importantes que nous sommes si prudents. Quoinous soucions est foo-> x / y / z. Ils sont donc ajoutés à la file d'attente de lecture après la chasse / marqueur acquisition, ce qui garantit qu'ils sont en lecture seule après avoir lu foo-> prêt.

Notez également que c'est généralement exactement les mêmes barrières utilisées pour verrouiller et déverrouiller un mutex / CriticalSection / etc. (Acquisition par exemple le verrouillage (), la libération de déverrouillage ()).

  • Je suis sûr que ce (par exemple l'acquisition / release) est exactement ce que MS docs disent se pour la lecture / écriture des variables « volatiles » en C # (et éventuellement pour MS C ++, mais est non standard) . Voir http://msdn.microsoft.com/en-us /library/aa645755(VS.71).aspx y compris « Une lecture volatile a « acquérir la sémantique », qui est, il est garanti de se produire avant toute référence à la mémoire qui se produisent après ... »

  • pense java est le même, bien que je ne suis pas aussi familier. Je soupçonne que c'est exactement la même chose, parce que vous ne généralement pas besoin de plus de garanties que lecture-acquire / écriture libération.

  • Dans votre question, vous étiez sur la bonne voie quand on pense qu'il est vraiment tout ordre relatif - vous avez juste eu les ordonnancements en arrière (ie « les valeurs qui sont lues sont au moins aussi mise à jour comme le lit devant la barrière "-. non, lit devant la barrière sont sans importance, son lit APRÈS la barrière qui sont garantis APRÈS, vice-versa pour les écritures)

  • Et s'il vous plaît noter, comme mentionné, réordonner arrive sur les deux lectures et écritures, de sorte que l'aide d'une barrière sur un fil et non l'NE FONCTIONNERA PAS. à-dire une écriture à libération ne suffit pas sans la lire acquérir. à-dire même si vous écrivez dans l'ordre, on pouvait lire dans le mauvais ordre si vous n'avez pas utilisé les barrières de lecture pour aller avec les barrières d'écriture.

  • Enfin, notez que la programmation sans verrouillage et les architectures mémoire CPU peuvent être en réalité beaucoup plus compliqué que cela, mais le collage avec acquisition / release vous obtiendrez assez loin.

Autres conseils

volatile dans la plupart des langages de programmation ne signifie pas une véritable barrière de mémoire morte CPU, mais un ordre au compilateur de ne pas optimiser le lit via la mise en cache dans un registre. Cela signifie que le processus / thread lecture va obtenir la valeur « à terme ». Une technique courante consiste à déclarer un drapeau booléen volatile à être installé dans un gestionnaire de signaux et vérifié dans la boucle principale du programme.
En revanche les barrières de mémoire CPU sont fournis directement soit via des instructions CPU ou implicite avec certains mnémoniques assembleur (comme préfixe lock dans x86) et sont utilisés par exemple lorsque l'on parle aux périphériques matériels où l'ordre de lecture et d'écriture aux registres IO mappés en mémoire est importante ou de synchronisation des accès à la mémoire dans un environnement multi-traitement.
Pour répondre à votre question - non, barrière de mémoire ne garantit pas la valeur « dernier », mais garanties pour des opérations d'accès mémoire. Ceci est crucial par exemple dans sans blocage.
est l'une des amorces sur les barrières de mémoire CPU.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top