56 votes

Comment puis-je Lire les Barrières de la Mémoire et de la Volatilité

Certaines langues fournir un volatile modificateur qui est décrit comme l'exécution d'une "lecture de la mémoire de la barrière" avant la lecture de la mémoire que le dos d'une variable.

Une lecture de la mémoire de la barrière est souvent décrite comme une façon de s'assurer que le CPU a effectué le lit demandée avant la barrière avant qu'il effectue une lecture demandée après la barrière. Cependant, l'utilisation de cette définition, il semble qu'une rassis valeur peut encore être lu. En d'autres termes, l'exécution de lit dans un certain ordre ne semble pas signifier que la mémoire principale ou d'autres Processeurs doivent être consultés afin de s'assurer que les valeurs suivantes de lire réellement tenir compte de la dernière dans le système au moment de la lecture de la barrière ou de l'écrit par la suite après la lecture de la barrière.

Donc, ne volatils vraiment garantir qu'une mise à jour de la valeur est en lecture ou tout simplement (gasp!) que les valeurs lues sont au moins aussi à jour que le lit avant de la barrière? Ou une autre interprétation? Quelles sont les implications pratiques de cette réponse?

113voto

tony Points 2048

Il y a en lire les obstacles et d'écrire des obstacles; acquérir les obstacles et la libération des obstacles. Et plus (io vs mémoire, etc).

Les obstacles ne sont pas là pour contrôler la "dernière" de la valeur ou de la "fraîcheur" des valeurs. Ils sont là pour contrôler l'ordre relatif d'accès à la mémoire.

Écrire des barrières de contrôle de l'ordre de l'écrit. Parce que écrit à la mémoire sont lent (par rapport à la vitesse de la CPU), il est généralement une requête d'écriture de la file d'attente où les données sont affichées avant qu'ils ne "vraiment arriver". Bien qu'ils sont en file d'attente dans l'ordre, tandis que l'intérieur de la file d'attente de l'écrit peuvent être réorganisées. (Donc peut-être "file d'attente" n'est pas le meilleur nom...), Sauf si vous utilisez écrire des barrières pour empêcher la remise en ordre.

Lire les barrières de contrôle de l'ordre du lit. Parce que spéculatif de l'exécution (CPU ressemble à l'avance et les charges à partir de la mémoire au début) et en raison de l'existence de la mémoire tampon d'écriture (le PROCESSEUR va lire une valeur dans le tampon d'écriture au lieu de la mémoire si c'est là, - c'est à dire le CPU pense qu'il est juste écrit X = 5, alors pourquoi le lire, viens de voir que c'est toujours en attente de devenir 5 dans la mémoire tampon d'écriture) lit peut se produire hors de l'ordre.

Cela est vrai indépendamment de ce que le compilateur essaie de faire à l'égard de l'ordre du code généré. ie 'volatile' en C++ n'aidera pas ici, car il indique au compilateur de code de sortie de re-lire la valeur de "mémoire", il ne dit PAS le CPU comment/où à lire à partir de (c'est à dire de la "mémoire" est beaucoup de choses au niveau CPU).

Afin de lire/écrire les obstacles mis en place des blocs pour empêcher la réorganisation de la lecture/écriture des files d'attente (la lecture n'est pas tellement d'une file d'attente, mais la réorganisation des effets sont les mêmes).

Quels sont les types de blocs? - acquérir et/ou de la libération des blocs.

Acquérir - par exemple la lecture d'acquérir(x) permettra d'ajouter la lecture de x dans la lecture de la file d'attente et vider la file d'attente (pas vraiment vider la file d'attente, mais ajouter un marqueur dit de ne pas réorganiser quoi que ce soit avant cette lecture, qui est que si la file d'attente a été rincé). Plus tard (dans l'ordre de code) lit peuvent être réorganisés, mais pas avant la lecture de x.

Libération - par exemple écrire à libération(x, 5) vider (ou marqueur) la file d'attente, puis ajouter la requête d'écriture à l'écriture, à la file d'attente. Donc, plus tôt écrit ne se réorganiser pour arriver au bout de x = 5, mais remarque que, plus tard, d'écriture peuvent être réorganisées avant x = 5.

Notez que j'ai relié le lire à acquérir et à écrire avec libération, parce que ce est typique, mais différentes combinaisons sont possibles.

Acquérir et de presse sont considérés comme des "demi-barrières" ou "demi-barrières" parce qu'elles seulement s'arrêter à la réorganisation d'aller dans un sens.

Plein de barrière (ou une clôture) s'applique à la fois une acquisition et d'un presse - ie, pas de réorganisation.

Généralement, pour les lockfree de programmation, ou C# ou java 'volatile', ce que vous voulez/besoin est lecture d'acquérir et de l'écriture de presse.

ie

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, c'est une mauvaise façon de threads du programme. Serrures serait plus sûr. Mais juste pour illustrer les obstacles...

Après threadA() est en fait écrit foo, il faut écrire des foo->prêt DURER, durer, sinon les autres threads peut voir foo->prêt au début et à obtenir de mauvaises valeurs de x/y/z. Nous utilisons donc un write_release sur foo->prêt, qui, comme mentionné ci-dessus, de manière efficace "bouffées de chaleur" la file d'attente d'écriture (veiller à ce que x,y,z sont commis) puis ajoute le prêt=true demande à la file d'attente. Et puis, ajoute la barre d'=13 demande. Notez que depuis que nous avons juste utilisé une barrière release (pas complet) bar=13 peuvent être écrites avant le prêt. Mais nous n'avons pas de soins! à savoir, nous sommes en supposant que la barre n'est pas en changeant de données partagée.

Maintenant threadB() a besoin de savoir que lorsque nous disons "prêt" nous signifie vraiment prêt. Donc nous faisons read_acquire(foo->ready). Cette lecture est ajouté à la lecture de la file d'attente, PUIS la file d'attente est vidé. Notez que w = some_global , pourrait être encore dans la file d'attente. Donc foo->prêt peut être lu avant d' some_global. Mais encore une fois, nous n'avons pas de soins, car il ne fait pas partie de la important de données qui nous sont si prudents. Ce que nous ne se soucient que de foo->x/y/z. Donc, ils sont ajoutés à la lecture de la file d'attente après l'acquisition flush/marqueur, en garantissant qu'ils sont en lecture seulement après la lecture de foo->prêt.

A noter également, que c'est généralement exactement les mêmes obstacles utilisé pour le verrouillage et le déverrouillage d'un mutex/CriticalSection/etc. (c'est à dire acquérir sur lock(), sortie sur unlock() ).

Donc,

  • Je suis sûr que cela (c'est à dire acquérir/release) est exactement ce que MME docs dire qui se passe pour la lecture/écriture de 'volatile' les variables en C# (et éventuellement pour MS C++, mais c'est non-standard). Voir http://msdn.microsoft.com/en-us/library/aa645755%28VS.71%29.aspx y compris "Une lecture volatile a "acquérir" sémantique; c'est, c'est la garantie de se produire avant tout des références à la mémoire qui se produisent après..."

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

  • Dans votre question vous avez été sur l'écriture de suivi lorsque l'on pense que c'est vraiment tout au sujet de l'ordre - vous juste eu l'ordre vers l'arrière (c'est à dire "les valeurs lues sont au moins aussi à jour que le lit avant de la barrière? "- non, lit avant de la barrière sont sans importance, son lit APRÈS la barrière qui sont garantis pour être APRÈS, vice versa pour les écritures).

  • Et s'il vous plaît noter, comme mentionné, la réorganisation qui se passe sur les lectures et les écritures, donc uniquement à l'aide d'une barrière sur un thread et pas l'autre ne SERA PAS de TRAVAIL. c'est à dire une écriture de presse n'est pas assez, sans le lire,-acquérir. c'est à dire même si vous l'écrivez dans le bon ordre, il pourrait être lu dans le mauvais ordre si vous n'utilisez pas la lecture des barrières pour aller avec l'écriture des obstacles.

  • Et enfin, notez que sans verrouillage de la programmation et de la CPU architectures de mémoire peut être en réalité beaucoup plus compliqué que ça, mais en restant avec de l'acquisition/diffusion va vous faire aller assez loin.

9voto

Nikolai N Fetissov Points 52093

volatile dans la plupart des langages de programmation n'implique pas un vrai CPU lire la mémoire de la barrière, mais un ordre au compilateur de ne pas optimiser les lectures via la mise en cache dans un registre. Cela signifie que le processus de lecture/thread recevra la valeur "finalement". Une technique courante consiste à déclarer un booléen volatile drapeau à être mis dans un gestionnaire de signal et vérifié dans la boucle principale du programme.

En revanche PROCESSEUR mémoire obstacles sont fournies directement soit par l'intermédiaire d'instructions du PROCESSEUR ou implicite certaines assembleur mnémoniques (comme lock préfixe x86) et sont utilisés par exemple lorsque l'on parle de périphériques matériels où l'ordre de lecture et d'écriture de la mémoire mappée IO registres est important ou de la synchronisation des accès à la mémoire en multi-environnement de traitement.

Pour répondre à votre question, non, la mémoire de la barrière ne garantit pas la "dernière" de la valeur, mais des garanties de commande d'accès à la mémoire des opérations. C'est crucial, par exemple dans sans verrouillage de la programmation.

Ici est l'une des amorces sur les CPU les barrières de la mémoire.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X