J'ai lu quelques articles sur le volatile
mot - clé mais je n'ai pas pu comprendre son utilisation correcte. Pouvez-vous me dire à quoi cela doit servir en C # et en Java?
J'ai lu quelques articles sur le volatile
mot - clé mais je n'ai pas pu comprendre son utilisation correcte. Pouvez-vous me dire à quoi cela doit servir en C # et en Java?
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".
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 i
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.
i
marqué comme volatile
. 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 soit final
. 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.
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 = new Thing();
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 par something
, ils peuvent voir la valeur foo
changer avant que les écritures en mémoire dans le Thing
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 magasin foo
. Si foo
c'est le cas, volatile
le magasin foo
aura une sémantique de publication, et le matériel garantit que toutes les écritures avant l'écriture sur foo
sont visibles par les autres processeurs avant d'autoriser l'écriture foo
.
Comment est-il possible que les écritures foo
soient si mal réorganisées? Si le foo
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:
public class MySingleton {
private static object myLock = new object();
private static volatile MySingleton mySingleton = null;
private MySingleton() {
}
public static MySingleton GetInstance() {
if (mySingleton == null) { // 1st check
lock (myLock) {
if (mySingleton == null) { // 2nd (double) check
mySingleton = new MySingleton();
// 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 MySingleton
constructeur 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.
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é.
head
et tail
doivent être volatils pour empêcher le producteur de supposer que tail
cela ne changera pas et pour empêcher le consommateur de supposer que head
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 dans head
soit globalement visible.
Le mot-clé volatile a des significations différentes en Java et en C #.
À partir de la spécification du langage Java :
Un champ peut être déclaré volatile, auquel cas le modèle de mémoire Java garantit que tous les threads voient une valeur cohérente pour la variable.
À partir de la référence C # sur le mot clé volatile :
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.
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.
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.