Quelqu'un peut-il fournir une bonne explication du mot clé volatile en C #? Quels problèmes résout-il et lequel ne résout pas? Dans quels cas cela me sauvera-t-il de l'utilisation du verrouillage?
c#
multithreading
Doron Yaacoby
la source
la source
Réponses:
Je ne pense pas qu'il y ait une meilleure personne pour répondre à cela qu'Eric Lippert (souligné dans l'original):
Pour plus d'informations, voir:
la source
volatile
seront là grâce au verrouSi vous voulez obtenir un peu plus de détails techniques sur ce que fait le mot-clé volatile, envisagez le programme suivant (j'utilise DevStudio 2005):
En utilisant les paramètres du compilateur optimisé (version) standard, le compilateur crée l'assembleur suivant (IA32):
En regardant la sortie, le compilateur a décidé d'utiliser le registre ecx pour stocker la valeur de la variable j. Pour la boucle non volatile (la première), le compilateur a affecté i au registre eax. Assez simple. Il y a cependant quelques bits intéressants - l'instruction lea ebx, [ebx] est en fait une instruction nop à plusieurs octets, de sorte que la boucle passe à une adresse mémoire alignée sur 16 octets. L'autre est l'utilisation d'edx pour incrémenter le compteur de boucles au lieu d'utiliser une instruction inc eax. L'instruction add reg, reg a une latence plus faible sur quelques cœurs IA32 par rapport à l'instruction inc inc, mais n'a jamais une latence plus élevée.
Passons maintenant à la boucle avec le compteur de boucle volatile. Le compteur est stocké dans [esp] et le mot-clé volatile indique au compilateur que la valeur doit toujours être lue / écrite dans la mémoire et jamais assignée à un registre. Le compilateur va même jusqu'à ne pas charger / incrémenter / stocker en trois étapes distinctes (charger eax, inc eax, save eax) lors de la mise à jour de la valeur du compteur, au lieu de cela la mémoire est directement modifiée dans une seule instruction (un add mem , reg). La façon dont le code a été créé garantit que la valeur du compteur de boucles est toujours à jour dans le contexte d'un seul cœur de processeur. Aucune opération sur les données ne peut entraîner de corruption ou de perte de données (donc ne pas utiliser la charge / inc / store car la valeur peut changer pendant l'inc et donc être perdue sur le magasin). Étant donné que les interruptions ne peuvent être réparées qu'une fois l'instruction en cours terminée,
Une fois que vous avez introduit un deuxième processeur dans le système, le mot-clé volatile ne protège pas contre la mise à jour des données par un autre processeur en même temps. Dans l'exemple ci-dessus, vous auriez besoin que les données ne soient pas alignées pour obtenir une corruption potentielle. Le mot-clé volatile n'empêchera pas une corruption potentielle si les données ne peuvent pas être gérées de manière atomique, par exemple, si le compteur de boucle était de type long long (64 bits), alors il faudrait deux opérations 32 bits pour mettre à jour la valeur, au milieu de une interruption peut survenir et modifier les données.
Ainsi, le mot-clé volatile n'est bon que pour les données alignées qui sont inférieures ou égales à la taille des registres natifs de sorte que les opérations sont toujours atomiques.
Le mot-clé volatile a été conçu pour être utilisé avec des opérations d'E / S où l'E / S serait en constante évolution, mais avait une adresse constante, comme un périphérique UART mappé en mémoire, et le compilateur ne devrait pas continuer à réutiliser la première valeur lue à partir de l'adresse.
Si vous manipulez des données volumineuses ou avez plusieurs processeurs, vous aurez besoin d'un système de verrouillage de niveau supérieur (OS) pour gérer correctement l'accès aux données.
la source
Si vous utilisez .NET 1.1, le mot clé volatile est nécessaire lorsque vous effectuez un verrouillage à double vérification. Pourquoi? Parce qu'avant .NET 2.0, le scénario suivant pouvait entraîner un deuxième thread à accéder à un objet non null, mais pas entièrement construit:
Avant .NET 2.0, this.foo pouvait se voir attribuer la nouvelle instance de Foo, avant la fin de l'exécution du constructeur. Dans ce cas, un deuxième thread pourrait entrer (lors de l'appel du thread 1 au constructeur de Foo) et rencontrer les problèmes suivants:
Avant .NET 2.0, vous pouviez déclarer this.foo comme volatile pour contourner ce problème. Depuis .NET 2.0, vous n'avez plus besoin d'utiliser le mot clé volatile pour effectuer un verrouillage à double vérification.
Wikipedia a en fait un bon article sur le verrouillage à double vérification, et aborde brièvement ce sujet: http://en.wikipedia.org/wiki/Double-checked_locking
la source
foo
? Le thread 1 ne se verrouille-t-il pasthis.bar
et donc seul le thread 1 pourra initialiser foo à un moment donné? Je veux dire, vous vérifiez la valeur après leParfois, le compilateur optimise un champ et utilise un registre pour le stocker. Si le thread 1 fait une écriture dans le champ et qu'un autre thread y accède, puisque la mise à jour a été stockée dans un registre (et non en mémoire), le 2ème thread obtiendrait des données périmées.
Vous pouvez penser au mot clé volatile comme disant au compilateur "Je veux que vous stockiez cette valeur en mémoire". Cela garantit que le 2ème thread récupère la dernière valeur.
la source
À partir de MSDN : le modificateur volatile est généralement utilisé pour un champ auquel plusieurs threads accèdent sans utiliser l'instruction de verrouillage pour sérialiser l'accès. L'utilisation du modificateur volatile garantit qu'un thread récupère la valeur la plus récente écrite par un autre thread.
la source
Le CLR aime optimiser les instructions, donc lorsque vous accédez à un champ en code, il peut ne pas toujours accéder à la valeur actuelle du champ (cela peut provenir de la pile, etc.). Le marquage d'un champ
volatile
garantit que la valeur actuelle du champ est accessible par l'instruction. Cela est utile lorsque la valeur peut être modifiée (dans un scénario non verrouillable) par un thread simultané dans votre programme ou un autre code exécuté dans le système d'exploitation.Vous perdez évidemment une certaine optimisation, mais cela rend le code plus simple.
la source
J'ai trouvé cet article de Joydip Kanjilal très utile!
When you mark an object or a variable as volatile, it becomes a candidate for volatile reads and writes. It should be noted that in C# all memory writes are volatile irrespective of whether you are writing data to a volatile or a non-volatile object. However, the ambiguity happens when you are reading data. When you are reading data that is non-volatile, the executing thread may or may not always get the latest value. If the object is volatile, the thread always gets the most up-to-date value
Je vais juste le laisser ici pour référence
la source
Le compilateur modifie parfois l'ordre des instructions dans le code pour l'optimiser. Normalement, ce n'est pas un problème dans un environnement à un seul thread, mais cela peut être un problème dans un environnement à plusieurs threads. Voir l'exemple suivant:
Si vous exécutez t1 et t2, vous ne vous attendez à aucune sortie ni à "Valeur: 10" comme résultat. Il se peut que le compilateur commute la ligne à l'intérieur de la fonction t1. Si t2 s'exécute alors, il se pourrait que _flag ait une valeur de 5, mais que _value ait 0. La logique attendue pourrait donc être brisée.
Pour résoudre ce problème, vous pouvez utiliser un mot clé volatile que vous pouvez appliquer au champ. Cette instruction désactive les optimisations du compilateur afin que vous puissiez forcer l'ordre correct dans votre code.
Vous ne devez utiliser volatile que si vous en avez vraiment besoin, car il désactive certaines optimisations du compilateur, il nuira aux performances. Il n'est pas non plus pris en charge par tous les langages .NET (Visual Basic ne le prend pas en charge), ce qui entrave l'interopérabilité des langues.
la source
Donc, pour résumer tout cela, la bonne réponse à la question est: si votre code s'exécute dans l'exécution 2.0 ou ultérieure, le mot clé volatile n'est presque jamais nécessaire et fait plus de mal que de bien s'il est utilisé inutilement. IE Ne l'utilisez jamais. MAIS dans les versions antérieures du runtime, il EST nécessaire pour un verrouillage correct de la double vérification sur les champs statiques. Champs spécifiquement statiques dont la classe a un code d'initialisation de classe statique.
la source
plusieurs threads peuvent accéder à une variable. La dernière mise à jour concernera la variable
la source