web-dev-qa-db-fra.com

A quoi sert le mot clé "volatile"?

J'ai lu des articles sur le mot clé volatile mais je ne pouvais pas en comprendre l'utilisation correcte. Pourriez-vous me dire à quoi ça sert en C # et en Java?

114
Mircea

Pour "C #" et pour 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 pouvant entraîner des problèmes si la variable change "en dehors de son contrôle".

81
Will A

Considérons cet exemple:

int i = 5;
System.out.println(i);

Le compilateur peut optimiser ceci pour simplement imprimer 5, comme ceci:

System.out.println(5);

Cependant, s'il existe un autre thread susceptible de modifier i, il s'agit d'un comportement incorrect. Si un autre thread modifie i en 6, la version optimisée en affichera quand même 5.

Le mot clé volatile empêche cette optimisation et cette mise en cache. Il est donc utile lorsqu'une variable peut être modifiée par un autre thread.

154
Sjoerd

Pour comprendre l'effet de 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. Toute modification effectuée par le thread A dans son cache local ne sera pas visible par le thread B.

  • La variable est volatile

Lorsque des 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, que les threads ne doivent pas faire confiance aux valeurs de ces variables à moins qu'elles ne soient lues directement dans la mémoire principale.

Alors, quand rendre une variable volatile?

Lorsque vous avez une variable accessible par plusieurs 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 un autre thread/processus/en dehors du programme.

36
Saurabh Patil

Le mot-clé volatile a des significations différentes dans les deux Java et C #.

Java

De la spécification de langage Java :

Un champ peut être déclaré volatil, auquel cas le modèle de mémoire Java) garantit que tous les threads voient une valeur cohérente pour la variable.

C #

À partir de la référence C # sur mot-clé volatil :

Le mot clé volatile indique qu'un champ peut être modifié dans le programme par un élément tel que le système d'exploitation, le matériel ou un thread en cours d'exécution.

34
krock

Les lectures de champs volatiles ont une sémantique acquise . Cela signifie qu'il est garanti que la mémoire lue à partir de la variable volatile se produira avant toute lecture ultérieure de la mémoire. Il empêche le compilateur d'effectuer la réorganisation, et si le matériel le requiert (processeur faiblement commandé), il utilisera une instruction spéciale pour que le matériel supprime toutes les lectures effectuées après la lecture volatile mais démarrées prématurément de manière spéculative. empêchez-les d’être émises tôt, en empêchant toute charge spéculative de se produire entre l’émission de la charge acquise et sa mise hors service.

Les écritures de champs volatiles ont la sémantique de publication . Cela signifie qu'il est garanti que toute écriture en mémoire dans la variable volatile sera garantie d'être retardée jusqu'à ce que toutes les écritures en mémoire précédentes soient visibles pour les autres processeurs.

Prenons l'exemple suivant:

something.foo = new Thing();

Si foo est une variable membre d'une classe et que les autres processeurs ont accès à l'instance d'objet désignée par something, ils risquent de voir la valeur foo change before la mémoire écrite dans le constructeur Thing est globalement visible! C'est ce que signifie "mémoire faiblement ordonnée". Cela peut se produire même si le compilateur a tous les magasins du constructeur avant le magasin à foo. Si foo est volatile, le magasin dans foo aura la sémantique de publication et le matériel garantit que toutes les écritures avant l'écriture dans foo sont visibles. à d'autres processeurs avant de permettre l'écriture sur foo.

Comment est-il possible que les écritures sur foo soient si mal réorganisées? Si la ligne de cache contenant foo est dans le cache et que les magasins du constructeur ont manqué le cache, il est possible que le magasin se termine beaucoup plus tôt que les écritures manquées.

L'architecture (terrible) Itanium 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 car des éléments tels que les verrous constitueront une barrière de mémoire complète, essentiellement la même chose que l'acquisition et la publication de la sémantique en même temps. Aucune charge à l'intérieur du verrou ne peut être exécutée de manière spéculative avant que le verrou ne soit acquis. Elles sont retardées jusqu'à ce que le verrou soit acquis. Aucun magasin ne peut être retardé lors du déverrouillage d'un 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 à double vérification". Le but de ce modèle est d’éviter de toujours avoir à acquérir un verrou pour initialiser un objet paresseux.

Snagged de 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 constructeur MySingleton risquent de ne pas être visibles pour les autres processeurs avant le magasin pour mySingleton. Si cela se produit, les autres threads qui jettent un coup d'œil à mySingleton n'obtiendront pas de verrou et ne récupéreront pas nécessairement les écritures au constructeur.

volatile n'empêche jamais la mise en cache. Cela garantit l’ordre dans lequel les autres processeurs "voient" écrivent. Une version de magasin retardera un magasin jusqu'à ce que toutes les écritures en attente soient terminées et qu'un cycle de bus ait été émis pour inviter les autres processeurs à supprimer/écrire leur ligne de cache si les lignes correspondantes sont mises en cache. Une acquisition de charge effacera toutes les lectures spéculées, garantissant qu'elles ne seront pas des valeurs obsolètes du passé.

30
doug65536

En Java, le terme "volatile" indique à la machine virtuelle Java que la variable peut être utilisée simultanément par plusieurs threads. Par conséquent, certaines optimisations courantes ne peuvent pas être appliquées.

Notamment la situation dans laquelle les deux threads accédant à la même variable s'exécutent sur des processeurs distincts de la même machine. Il est très courant que les CPU mettent en cache de manière agressive les données qu’il contient, 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 dans la mémoire principale, au lieu de laisser le cache se vider lui-même, afin que le CPU2 puisse voir la valeur mise à jour (encore une fois en ignorant toutes les caches sur le chemin).

Lorsque vous lisez des données non volatiles, le thread en cours d'exécution peut obtenir ou non toujours la valeur mise à jour. Mais si l'objet est volatile, le thread obtient toujours la valeur la plus récente.

1
Subhash Saini