J'ai lu quelques articles sur le volatilemot - clé mais je n'ai pas pu comprendre son utilisation correcte. Pouvez-vous me dire à quoi cela doit servir en C # et en Java?
L'un des problèmes avec volatile est que cela signifie plus d'une chose. Le fait que le compilateur ne fasse pas d’optimisations géniales est un héritage du C. Cela signifie également que des barrières de mémoire doivent être utilisées lors de l'accès. Mais dans la plupart des cas, cela ne coûte que les performances et / ou déroute les gens. : P
AnorZaken
Réponses:
93
Pour C # et Java, «volatile» indique au compilateur que la valeur d'une variable ne doit jamais être mise en cache car sa valeur peut changer en dehors de la portée du programme lui-même. Le compilateur évitera alors toute optimisation qui pourrait entraîner des problèmes si la variable change "hors de son contrôle".
Faux. N'empêche pas la mise en cache. Voyez ma réponse.
doug65536
168
Prenons cet exemple:
int i =5;System.out.println(i);
Le compilateur peut optimiser cela pour simplement imprimer 5, comme ceci:
System.out.println(5);
Cependant, s'il y a un autre thread qui peut changer i, c'est le mauvais comportement. Si un autre thread devient i6, la version optimisée imprimera toujours 5.
Le volatilemot-clé empêche une telle optimisation et mise en cache, et est donc utile lorsqu'une variable peut être modifiée par un autre thread.
Je pense que l'optimisation serait toujours valide avec imarqué comme volatile. En Java, il s'agit de relations qui se produisent avant .
Tom Hawtin - tackline
Merci pour la publication, donc en quelque sorte volatile a des connexions avec verrouillage variable?
Mircea
@Mircea: C'est ce qu'on m'a dit que marquer quelque chose comme volatile était une question: marquer un champ comme volatil utiliserait un mécanisme interne pour permettre aux threads de voir une valeur cohérente pour la variable donnée, mais cela n'est pas mentionné dans la réponse ci-dessus ... peut-être que quelqu'un peut le confirmer ou non? Merci
npinti
5
@Sjoerd: Je ne suis pas sûr de comprendre cet exemple. Si iest une variable locale, aucun autre thread ne peut la modifier de toute façon. S'il s'agit d'un champ, le compilateur ne peut pas optimiser l'appel à moins que ce ne soit final. Je ne pense pas que le compilateur puisse faire des optimisations en supposant qu'un champ "regarde" finalquand il n'est pas explicitement déclaré comme tel.
polygenelubricants
1
C # et java ne sont pas C ++. Ce n'est pas correct. Cela n'empêche pas la mise en cache et n'empêche pas l'optimisation. Il s'agit de la sémantique de lecture-acquisition et de libération de stockage, qui sont nécessaires sur les architectures de mémoire faiblement ordonnées. Il s'agit d'une exécution spéculative.
doug65536
40
Pour comprendre ce que fait la volatilité sur une variable, il est important de comprendre ce qui se passe lorsque la variable n'est pas volatile.
La variable est non volatile
Lorsque deux threads A et B accèdent à une variable non volatile, chaque thread conservera une copie locale de la variable dans son cache local. Toutes les modifications effectuées par le thread A dans son cache local ne seront pas visibles par le thread B.
La variable est volatile
Lorsque les variables sont déclarées volatiles, cela signifie essentiellement que les threads ne doivent pas mettre en cache une telle variable ou en d'autres termes, les threads ne doivent pas faire confiance aux valeurs de ces variables à moins qu'elles ne soient directement lues à partir de la mémoire principale.
Alors, quand rendre une variable volatile?
Lorsque vous avez une variable accessible par de nombreux threads et que vous voulez que chaque thread obtienne la dernière valeur mise à jour de cette variable même si la valeur est mise à jour par tout autre thread / processus / en dehors du programme.
Faux. Cela n'a rien à voir avec «empêcher la mise en cache». Il s'agit de réorganiser, par le compilateur, OU le matériel du processeur via une exécution spéculative.
doug65536
37
Les lectures de champs volatils ont acquis une sémantique . Cela signifie qu'il est garanti que la mémoire lue à partir de la variable volatile se produira avant toute lecture de mémoire suivante. Il empêche le compilateur d'effectuer la réorganisation, et si le matériel l'exige (CPU faiblement ordonné), il utilisera une instruction spéciale pour que le matériel vide toutes les lectures qui se produisent après la lecture volatile mais qui ont été démarrées de manière spéculative tôt, ou le CPU pourrait empêcher leur émission anticipée en premier lieu, en empêchant toute charge spéculative de se produire entre l'émission de l'acquisition de charge et son retrait.
Les écritures de champs volatiles ont une sémantique de publication . Cela signifie qu'il est garanti que toute écriture mémoire dans la variable volatile sera retardée jusqu'à ce que toutes les écritures mémoire précédentes soient visibles par les autres processeurs.
Prenons l'exemple suivant:
something.foo =newThing();
Si fooest une variable membre dans une classe et que d'autres processeurs ont accès à l'instance d'objet référencée par something, ils peuvent voir la valeur foochanger avant que les écritures en mémoire dans le Thingconstructeur ne soient globalement visibles! C'est ce que signifie «mémoire faiblement ordonnée». Cela peut se produire même si le compilateur a tous les magasins dans le constructeur avant le magasin foo. Si fooc'est le cas, volatilele magasin fooaura une sémantique de publication, et le matériel garantit que toutes les écritures avant l'écriture sur foosont visibles par les autres processeurs avant d'autoriser l'écriture foo.
Comment est-il possible que les écritures foosoient si mal réorganisées? Si le foostockage de la ligne de cache se trouve dans le cache et que les magasins du constructeur ont manqué le cache, il est alors possible que le magasin se termine beaucoup plus tôt que les écritures dans le cache échouent.
L'architecture Itanium (horrible) d'Intel avait une mémoire faiblement ordonnée. Le processeur utilisé dans la XBox 360 d'origine avait une mémoire faiblement ordonnée. De nombreux processeurs ARM, y compris le très populaire ARMv7-A, ont une mémoire faiblement ordonnée.
Les développeurs ne voient souvent pas ces courses de données parce que des choses comme les verrous feront une barrière de mémoire complète, essentiellement la même chose que d'acquérir et de publier une sémantique en même temps. Aucune charge à l'intérieur de la serrure ne peut être exécutée de manière spéculative avant l'acquisition de la serrure, elles sont retardées jusqu'à ce que la serrure soit acquise. Aucun stockage ne peut être retardé lors d'une libération de verrou, l'instruction qui libère le verrou est retardée jusqu'à ce que toutes les écritures effectuées à l'intérieur du verrou soient globalement visibles.
Un exemple plus complet est le modèle «Verrouillage vérifié». Le but de ce modèle est d'éviter d'avoir à toujours acquérir un verrou pour initialiser paresseusement un objet.
Accroché sur Wikipedia:
publicclassMySingleton{privatestaticobject myLock =newobject();privatestaticvolatileMySingleton mySingleton =null;privateMySingleton(){}publicstaticMySingletonGetInstance(){if(mySingleton ==null){// 1st checklock(myLock){if(mySingleton ==null){// 2nd (double) check
mySingleton =newMySingleton();// Write-release semantics are implicitly handled by marking// mySingleton with 'volatile', which inserts the necessary memory// barriers between the constructor call and the write to mySingleton.// The barriers created by the lock are not sufficient because// the object is made visible before the lock is released.}}}// The barriers created by the lock are not sufficient because not all threads// will acquire the lock. A fence for read-acquire semantics is needed between// the test of mySingleton (above) and the use of its contents. This fence// is automatically inserted because mySingleton is marked as 'volatile'.return mySingleton;}}
Dans cet exemple, les magasins du MySingletonconstructeur peuvent ne pas être visibles par les autres processeurs avant le magasin mySingleton. Si cela se produit, les autres threads qui regardent mySingleton n'acquériront pas de verrou et ils ne récupéreront pas nécessairement les écritures dans le constructeur.
volatilen'empêche jamais la mise en cache. Ce qu'il fait, c'est garantir l'ordre dans lequel les autres processeurs "voient" les écritures. Une libération du magasin retardera un magasin jusqu'à ce que toutes les écritures en attente soient terminées et qu'un cycle de bus ait été émis, indiquant aux autres processeurs de rejeter / réécrire leur ligne de cache s'ils ont les lignes pertinentes mises en cache. Une acquisition de charge videra toutes les lectures spéculées, garantissant qu'elles ne seront pas des valeurs périmées du passé.
Bonne explication. Aussi bon exemple de verrouillage à double contrôle. Cependant, je ne sais toujours pas quand utiliser car je suis inquiet pour les aspects de la mise en cache. Si j'écris une implémentation de file d'attente où un seul thread sera en train d'écrire et qu'un seul thread sera en lecture, puis-je m'en sortir sans verrous et marquer simplement mes "pointeurs" tête et queue comme volatils? Je veux m'assurer que le lecteur et le rédacteur voient les valeurs les plus à jour.
nickdu
Les deux headet taildoivent être volatils pour empêcher le producteur de supposer que tailcela ne changera pas et pour empêcher le consommateur de supposer que headcela ne changera pas. De plus, headdoit être volatile pour garantir que les écritures de données de la file d'attente sont globalement visibles avant que le stockage dans headsoit globalement visible.
doug65536
+1, les termes comme "dernier /" le plus mis à jour "impliquent malheureusement un concept de valeur correcte singulière. En réalité, deux concurrents peuvent franchir une ligne d'arrivée exactement au même moment - sur un processeur, deux cœurs peuvent demander une écriture exactement au même moment . Après tout, les cœurs ne font pas le travail à tour de rôle - cela rendrait le multi-cœur inutile. Une bonne réflexion / conception multi-thread ne devrait pas se concentrer sur essayer de forcer la «dernière nouveauté» de bas niveau - intrinsèquement fausse car un verrou force juste les cœurs à sélectionner arbitrairement un haut-parleur à la fois sans équité - mais plutôt essayer de concevoir le besoin d'un tel concept artificiel.
AnorZaken
34
Le mot-clé volatile a des significations différentes en Java et en C #.
Le mot-clé volatile indique qu'un champ peut être modifié dans le programme par quelque chose comme le système d'exploitation, le matériel ou un thread s'exécutant simultanément.
Merci beaucoup pour la publication, comme je l'ai compris en Java, cela agit comme le verrouillage de cette variable dans un contexte de thread, et en C # si elle est utilisée, la valeur de la variable peut être modifiée non seulement à partir du programme, des facteurs externes tels que le système d'exploitation peuvent modifier sa valeur ( pas de verrouillage implicite) ... S'il vous plaît laissez-moi savoir si j'ai bien compris ces différences ...
Mircea
@Mircea en Java, il n'y a pas de verrouillage, cela garantit simplement que la valeur la plus à jour de la variable volatile sera utilisée.
krock
Java promet-il une sorte de barrière mémoire, ou est-ce comme C ++ et C # en promettant seulement de ne pas optimiser la référence?
Steven Sudit
La barrière mémoire est un détail d'implémentation. Ce que Java promet en fait, c'est que toutes les lectures verront la valeur écrite par l'écriture la plus récente.
Stephen C
1
@StevenSudit Oui, si le matériel nécessite une barrière ou charger / acquérir ou stocker / libérer, il utilisera ces instructions. Voyez ma réponse.
doug65536
9
En Java, «volatile» est utilisé pour indiquer à la JVM que la variable peut être utilisée par plusieurs threads en même temps, de sorte que certaines optimisations courantes ne peuvent pas être appliquées.
Notamment la situation où les deux threads accédant à la même variable s'exécutent sur des processeurs séparés dans la même machine. Il est très courant que les processeurs mettent en cache de manière agressive les données qu'ils contiennent car l'accès à la mémoire est beaucoup plus lent que l'accès au cache. Cela signifie que si les données sont mises à jour dans CPU1, elles doivent immédiatement passer par tous les caches et vers la mémoire principale au lieu de quand le cache décide de se vider, afin que CPU2 puisse voir la valeur mise à jour (encore une fois en ignorant tous les caches en cours de route).
Lorsque vous lisez des données non volatiles, le thread en cours d'exécution peut ou ne peut pas toujours obtenir la valeur mise à jour. Mais si l'objet est volatil, le thread obtient toujours la valeur la plus à jour.
Le mot-clé volatile vous donnera la valeur la plus mise à jour plutôt que la valeur mise en cache.
Subhash Saini
0
Volatile résout le problème de la concurrence. Pour synchroniser cette valeur. Ce mot clé est principalement utilisé dans un thread. Lorsque plusieurs threads mettent à jour la même variable.
Je ne pense pas que cela "résout" le problème. C'est un outil qui aide dans certaines circonstances. Ne comptez pas sur volatile pour les situations où un verrou est nécessaire, comme dans une condition de concurrence.
Réponses:
Pour C # et Java, «volatile» indique au compilateur que la valeur d'une variable ne doit jamais être mise en cache car sa valeur peut changer en dehors de la portée du programme lui-même. Le compilateur évitera alors toute optimisation qui pourrait entraîner des problèmes si la variable change "hors de son contrôle".
la source
Prenons cet exemple:
Le compilateur peut optimiser cela pour simplement imprimer 5, comme ceci:
Cependant, s'il y a un autre thread qui peut changer
i
, c'est le mauvais comportement. Si un autre thread devienti
6, la version optimisée imprimera toujours 5.Le
volatile
mot-clé empêche une telle optimisation et mise en cache, et est donc utile lorsqu'une variable peut être modifiée par un autre thread.la source
i
marqué commevolatile
. En Java, il s'agit de relations qui se produisent avant .i
est une variable locale, aucun autre thread ne peut la modifier de toute façon. S'il s'agit d'un champ, le compilateur ne peut pas optimiser l'appel à moins que ce ne soitfinal
. Je ne pense pas que le compilateur puisse faire des optimisations en supposant qu'un champ "regarde"final
quand il n'est pas explicitement déclaré comme tel.Pour comprendre ce que fait la volatilité sur une variable, il est important de comprendre ce qui se passe lorsque la variable n'est pas volatile.
Lorsque deux threads A et B accèdent à une variable non volatile, chaque thread conservera une copie locale de la variable dans son cache local. Toutes les modifications effectuées par le thread A dans son cache local ne seront pas visibles par le thread B.
Lorsque les variables sont déclarées volatiles, cela signifie essentiellement que les threads ne doivent pas mettre en cache une telle variable ou en d'autres termes, les threads ne doivent pas faire confiance aux valeurs de ces variables à moins qu'elles ne soient directement lues à partir de la mémoire principale.
Alors, quand rendre une variable volatile?
Lorsque vous avez une variable accessible par de nombreux threads et que vous voulez que chaque thread obtienne la dernière valeur mise à jour de cette variable même si la valeur est mise à jour par tout autre thread / processus / en dehors du programme.
la source
Les lectures de champs volatils ont acquis une sémantique . Cela signifie qu'il est garanti que la mémoire lue à partir de la variable volatile se produira avant toute lecture de mémoire suivante. Il empêche le compilateur d'effectuer la réorganisation, et si le matériel l'exige (CPU faiblement ordonné), il utilisera une instruction spéciale pour que le matériel vide toutes les lectures qui se produisent après la lecture volatile mais qui ont été démarrées de manière spéculative tôt, ou le CPU pourrait empêcher leur émission anticipée en premier lieu, en empêchant toute charge spéculative de se produire entre l'émission de l'acquisition de charge et son retrait.
Les écritures de champs volatiles ont une sémantique de publication . Cela signifie qu'il est garanti que toute écriture mémoire dans la variable volatile sera retardée jusqu'à ce que toutes les écritures mémoire précédentes soient visibles par les autres processeurs.
Prenons l'exemple suivant:
Si
foo
est une variable membre dans une classe et que d'autres processeurs ont accès à l'instance d'objet référencée parsomething
, ils peuvent voir la valeurfoo
changer avant que les écritures en mémoire dans leThing
constructeur ne soient globalement visibles! C'est ce que signifie «mémoire faiblement ordonnée». Cela peut se produire même si le compilateur a tous les magasins dans le constructeur avant le magasinfoo
. Sifoo
c'est le cas,volatile
le magasinfoo
aura une sémantique de publication, et le matériel garantit que toutes les écritures avant l'écriture surfoo
sont visibles par les autres processeurs avant d'autoriser l'écriturefoo
.Comment est-il possible que les écritures
foo
soient si mal réorganisées? Si lefoo
stockage de la ligne de cache se trouve dans le cache et que les magasins du constructeur ont manqué le cache, il est alors possible que le magasin se termine beaucoup plus tôt que les écritures dans le cache échouent.L'architecture Itanium (horrible) d'Intel avait une mémoire faiblement ordonnée. Le processeur utilisé dans la XBox 360 d'origine avait une mémoire faiblement ordonnée. De nombreux processeurs ARM, y compris le très populaire ARMv7-A, ont une mémoire faiblement ordonnée.
Les développeurs ne voient souvent pas ces courses de données parce que des choses comme les verrous feront une barrière de mémoire complète, essentiellement la même chose que d'acquérir et de publier une sémantique en même temps. Aucune charge à l'intérieur de la serrure ne peut être exécutée de manière spéculative avant l'acquisition de la serrure, elles sont retardées jusqu'à ce que la serrure soit acquise. Aucun stockage ne peut être retardé lors d'une libération de verrou, l'instruction qui libère le verrou est retardée jusqu'à ce que toutes les écritures effectuées à l'intérieur du verrou soient globalement visibles.
Un exemple plus complet est le modèle «Verrouillage vérifié». Le but de ce modèle est d'éviter d'avoir à toujours acquérir un verrou pour initialiser paresseusement un objet.
Accroché sur Wikipedia:
Dans cet exemple, les magasins du
MySingleton
constructeur peuvent ne pas être visibles par les autres processeurs avant le magasinmySingleton
. Si cela se produit, les autres threads qui regardent mySingleton n'acquériront pas de verrou et ils ne récupéreront pas nécessairement les écritures dans le constructeur.volatile
n'empêche jamais la mise en cache. Ce qu'il fait, c'est garantir l'ordre dans lequel les autres processeurs "voient" les écritures. Une libération du magasin retardera un magasin jusqu'à ce que toutes les écritures en attente soient terminées et qu'un cycle de bus ait été émis, indiquant aux autres processeurs de rejeter / réécrire leur ligne de cache s'ils ont les lignes pertinentes mises en cache. Une acquisition de charge videra toutes les lectures spéculées, garantissant qu'elles ne seront pas des valeurs périmées du passé.la source
head
ettail
doivent être volatils pour empêcher le producteur de supposer quetail
cela ne changera pas et pour empêcher le consommateur de supposer quehead
cela ne changera pas. De plus,head
doit être volatile pour garantir que les écritures de données de la file d'attente sont globalement visibles avant que le stockage danshead
soit globalement visible.Le mot-clé volatile a des significations différentes en Java et en C #.
Java
À partir de la spécification du langage Java :
C #
À partir de la référence C # sur le mot clé volatile :
la source
En Java, «volatile» est utilisé pour indiquer à la JVM que la variable peut être utilisée par plusieurs threads en même temps, de sorte que certaines optimisations courantes ne peuvent pas être appliquées.
Notamment la situation où les deux threads accédant à la même variable s'exécutent sur des processeurs séparés dans la même machine. Il est très courant que les processeurs mettent en cache de manière agressive les données qu'ils contiennent car l'accès à la mémoire est beaucoup plus lent que l'accès au cache. Cela signifie que si les données sont mises à jour dans CPU1, elles doivent immédiatement passer par tous les caches et vers la mémoire principale au lieu de quand le cache décide de se vider, afin que CPU2 puisse voir la valeur mise à jour (encore une fois en ignorant tous les caches en cours de route).
la source
Lorsque vous lisez des données non volatiles, le thread en cours d'exécution peut ou ne peut pas toujours obtenir la valeur mise à jour. Mais si l'objet est volatil, le thread obtient toujours la valeur la plus à jour.
la source
Volatile résout le problème de la concurrence. Pour synchroniser cette valeur. Ce mot clé est principalement utilisé dans un thread. Lorsque plusieurs threads mettent à jour la même variable.
la source