À quoi sert le mot-clé «volatile»?

130

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?

Mircea
la source
1
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".

Will A
la source
@Tom - dûment noté, monsieur - et modifié.
Will A
11
C'est encore beaucoup plus subtil que ça.
Tom Hawtin - tackline
1
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.

Sjoerd
la source
3
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.

Saurabh Patil
la source
2
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 = new Thing();

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:

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 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é.

doug65536
la source
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 #.

Java

À 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.

C #

À 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.

Krock
la source
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).

Thorbjørn Ravn Andersen
la source
1

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.

Subhash Saini
la source
1
Pouvez-vous reformuler votre réponse?
Anirudha Gupta
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.

Shiv Ratan Kumar
la source
1
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.
Scratte le