783 votes

Volatile vs Interlocked vs serrure

Disons qu'une classe a un public int counter champ qui est accessible par plusieurs threads. Ce type int n'est incrémentée ou décrémentée.

Pour augmenter ce domaine, l'approche qui doit être utilisé, et pourquoi?

  • lock(this.locker) this.counter++;
  • Interlocked.Increment(ref this.counter);
  • Changer le modificateur d'accès de l' counter de public volatile

Maintenant que j'ai découvert, volatile,, j'ai été en supprimant nombre lock des déclarations et de l'utilisation de Interlocked. Mais est-il une raison de ne pas le faire?

995voto

Orion Edwards Points 54939

Pire (ne fait pas travailler)

Changer le modificateur d'accès de l' counter de public volatile

Comme d'autres personnes l'ont mentionné, ce sur son propre n'est en fait pas du tout en sécurité. Le point de volatile , c'est que plusieurs threads s'exécutant sur plusieurs CPU peut et va mettre en cache des données et re-instructions de commande.

Si c'est pas volatile, et le PROCESSEUR UN incrémente d'une valeur, puis UC B peut ne pas voir que la valeur incrémentée jusqu'à ce que quelque temps plus tard, ce qui peut causer des problèmes.

Si c'est volatile, ce s'assure juste les 2 CPU voir les mêmes données en même temps. Cela ne les empêche pas du tout de l'entrelacement de leurs lectures et les opérations d'écriture qui est le problème que vous essayez d'éviter.

Deuxième Meilleur:

lock(this.locker) this.counter++;

C'est sécuritaire de le faire (à condition que vous n'oubliez pas de lock partout d'autre que vous d'accéder this.counter). Il empêche les autres threads de s'exécuter tout autre code qui est gardé par locker. L'utilisation de verrous aussi, évite les multi-processeurs, la réorganisation des problèmes comme ci-dessus, ce qui est excellent.

Le problème est, le verrouillage est lente, et si vous ré-utiliser l' locker dans un autre lieu qui n'est pas vraiment lié alors vous pouvez vous retrouver bloquer vos autres threads pour aucune raison.

Meilleur

Interlocked.Increment(ref this.counter);

Ce qui est sûr, c'est l'efficacité de la lecture, de l'incrément, et d'écrire en "one hit" qui ne peut pas être interrompu. De ce fait, il ne sera pas affecter n'importe quel autre code et vous n'avez pas besoin de vous souvenir de verrouillage de l'extérieur. Il est également très rapide (comme MSDN dit, sur les modernes CPU c'est souvent littéralement un seul PROCESSEUR de l'instruction).

Je ne suis pas entièrement sûr cependant si il se retrouve autour d'autres CPU est la réorganisation des choses, ou si vous avez besoin de combiner volatile avec l'incrément.

Note de bas de page: Ce volatile est réellement bon pour.

En tant que volatile n'empêche pas ce genre de multithreading questions, quel est-il? Un bon exemple est à dire que vous avez 2 fils, l'un qui écrit toujours dans une variable (disons queueLength), et celui qui lit toujours de la même variable.

Si queueLength n'est pas volatile, Un thread peut écrire 5 fois, mais le fil B peut voir ces écritures comme étant en retard (ou même potentiellement dans le mauvais ordre).

Une solution serait de verrouillage, mais vous pouvez aussi, dans cette situation, l'utilisation volatile. Cela permettrait de s'assurer que le fil B verrez toujours les plus up-to-date de chose qu'Un thread a écrit. Notez cependant que cette logique seulement fonctionne si vous avez des écrivains qui n'ont jamais lu, et les lecteurs qui n'ont jamais écrire, et si la chose que vous avez écrit est une valeur atomique. Dès que vous faites une seule lecture-modification-écriture, vous devez aller à Contrefil des opérations ou de l'utilisation d'un Verrou.

164voto

Jon Skeet Points 692016

EDIT: Comme indiqué dans les commentaires, ces jours, je suis heureux, Interlocked pour le cas d'une seule variable , où il est bien évidemment d'accord. Quand cela devient plus compliqué, je vais encore revenir à verrouillage...

À l'aide de volatile ne va pas aider quand vous en avez besoin pour incrémenter - parce que la lecture et l'écriture sont des instructions distinctes. Un autre thread pourrait changer la valeur une fois que vous avez lu mais avant de vous écrire de nouveau.

Personnellement, j'ai presque toujours juste lock - il est plus facile d'obtenir le droit d'une manière qui est de toute évidence que soit la volatilité ou Contrefil.Incrément. Pour autant que je suis concerné, sans verrouillage multi-threading est pour de vrai filetage experts, dont je ne suis pas un. Si Joe Duffy et son équipe construire de belles bibliothèques paralléliser les choses, sans autant de verrouillage comme quelque chose que j'avais à construire, c'est fabuleux, et je vais l'utiliser dans un battement de cœur - mais quand je fais le filetage moi-même, j'essaie de garder les choses simples.

48voto

Michael Damatov Points 5453

" " ne remplace pas ! Il fait juste en sorte que la variable n’est pas mis en cache, mais utilisée directement.

Incrémenter une variable nécessite en fait trois opérations :

  1. lire
  2. incrément
  3. écrire

``effectue toutes les trois parties en une seule opération atomique.

45voto

Zach Saw Points 1819

Verrouiller ou contrefil incrément est ce que vous cherchez.

Volatile est certainement pas ce que vous cherchez - il dit au compilateur de traiter la variable comme toujours en évolution, même si le chemin d'accès au code permet au compilateur d'optimiser une lecture de la mémoire autrement.

par exemple

while (m_Var)
{ }

si m_Var est définie sur false dans un autre fil, mais il n'est pas déclaré comme volatile, le compilateur est libre de faire une boucle infinie (mais ne signifie pas que ce sera toujours le cas), en faisant cela, cochez la case à l'encontre d'un PROCESSEUR inscrire (par exemple, EAX parce que c'était ce que m_Var a été récupérées depuis le début), au lieu d'émettre une autre lecture de l'emplacement de mémoire de m_Var (ce qui peut être mis en cache, on ne sait pas et ne font pas attention et c'est le point de cohérence de cache de x86/x64). Tous les posts plus tôt par d'autres qui ont mentionné l'instruction de la réorganisation de simplement montrer qu'ils ne comprennent pas x86/x64 architectures. Volatile n'a pas de problème de lecture/écriture des obstacles comme le laisse entendre le plus tôt postes en disant 'il empêche la réorganisation'. En fait, merci encore pour MESI protocole, nous sommes assurés du résultat que nous avons lu est toujours le même entre les Processeurs indépendamment du fait que les résultats réels ont été retirés de la mémoire physique ou tout simplement résider dans le local cache du CPU. Je ne vais pas aller trop loin dans les détails, mais soyez assuré que si cela se passe mal, Intel/AMD serait probablement question d'un processeur de rappel! Cela signifie également que nous ne devons pas nous soucier de l'ordre d'exécution, etc. Les résultats sont toujours garanti à la retraite dans l'ordre - sinon, nous sommes en peluche!

Avec Contrefil Incrément, le processeur n'a besoin de sortir, d'aller chercher la valeur de l'adresse donnée, puis d'incrémentation et de l'écrire, tout cela, tout en ayant la propriété exclusive de l'ensemble de la ligne de cache (lock xadd) pour s'assurer qu'aucun des autres processeurs pouvez modifier sa valeur.

Avec volatils, vous pourrez toujours retrouver avec seulement 1 instruction (en supposant que le JIT est efficace comme il se doit) - inc dword ptr [m_Var]. Cependant, le processeur (cpuA) ne demande pas la propriété exclusive de la ligne de cache, tout autant qu'il l'a fait avec le contrefil version. Comme vous pouvez l'imaginer, cela signifie que d'autres processeurs pourrait écrire une mise à jour de la valeur de retour de m_Var après qu'il a été lu par cpuA. Ainsi, au lieu de maintenant, après avoir incrémenté de la valeur de deux fois, vous vous retrouvez avec juste une fois.

Espérons que cela efface la question.

Pour plus d'informations, voir "Comprendre l'Impact de la Basse-Lock Techniques dans les Applications Multithread' - http://msdn.microsoft.com/en-au/magazine/cc163715.aspx

p.s. Ce qui a incité cette réponse tardive? Toutes les réponses ont été de manière manifestement inexacte (en particulier l'un marqué comme réponse) dans leur explication que j'ai juste eu à clarifier les choses pour quelqu'un d'autre la lecture de ce. haussements d'épaules

p.p.s. Je suis en supposant que la cible est x86/x64 et pas IA64 (il a un autre modèle de mémoire). Notez que Microsoft spécifications ECMA est vissé vers le haut, en ce qu'elle spécifie les plus faibles du modèle de mémoire au lieu de la plus forte (c'est toujours mieux de préciser à l'encontre de la plus forte du modèle de mémoire de sorte qu'il est compatible sur toutes les plateformes - sinon, le code qui irait à l'24-7 sur x86/x64 peut ne pas fonctionner du tout sur IA64 bien qu'Intel a mis en œuvre de même une forte mémoire de modèle pour l'architecture IA64) - Microsoft a admis eux-mêmes - http://blogs.msdn.com/b/cbrumme/archive/2003/05/17/51445.aspx.

18voto

Lou Franco Points 48823

Interloqué les fonctions ne se verrouille pas. Ils sont atomiques, ce qui signifie qu'ils peuvent remplir sans la possibilité d'un changement de contexte au cours de l'incrément. Donc, il n'y a aucune chance de blocage ou d'attendre.

Je dirais que vous devriez toujours la préfère à une serrure et d'un incrément.

Volatile est utile si vous avez besoin d'écrit dans un thread pour être lu dans un autre, et si vous voulez l'optimiseur de ne pas réorganiser les opérations sur une variable (parce que les choses sont en train d'arriver dans un autre thread que l'optimiseur ne sais pas). C'est un orthogonale choix de la façon dont vous l'attendiez.

C'est vraiment un très bon article si vous voulez en savoir plus sur le code sans verrouillage, et la bonne façon de l'écrire

http://www.ddj.com/hpc-high-performance-computing/210604448

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