2008-09-25 6 views
87

Je voudrais coder un petit programme qui illustre visuellement le comportement du mot-clé volatile. Idéalement, il devrait s'agir d'un programme qui effectue un accès simultané à un champ statique non volatil et qui obtient un comportement incorrect à cause de cela. Ajouter le mot-clé volatile dans le même programme devrait résoudre le problème.Illustrer l'utilisation du mot-clé volatile en C#

C'est quelque chose que je n'ai pas réussi à atteindre. Même en essayant plusieurs fois, en permettant l'optimisation, etc., j'obtiens toujours un comportement correct sans le mot clé 'volatile'.

Avez-vous une idée sur ce sujet? Savez-vous comment simuler un tel problème dans une application de démonstration simple? Cela dépend-il du matériel?

Répondre

97

J'ai réussi un exemple de travail!

L'idée principale reçue du wiki, mais avec quelques changements pour C#. L'article wiki démontre ceci pour le champ statique de C++, il semble que C# compile toujours soigneusement les requêtes aux champs statiques ...et je fais exemple avec non statique un:

Si vous exécutez cet exemple en Déclenc et sans débogueur (par exemple en utilisant les touches Ctrl + F5) puis la ligne while (test.foo != 255) sera optimisé pour « while (true) » et ce programme ne retourne jamais. Mais après avoir ajouté volatile mot-clé, vous obtenez toujours «OK».

class Test 
{ 
    /*volatile*/ int foo; 

    static void Main() 
    { 
     var test = new Test(); 

     new Thread(delegate() { Thread.Sleep(500); test.foo = 255; }).Start(); 

     while (test.foo != 255) ; 
     Console.WriteLine("OK"); 
    } 
} 
+5

Testé sur .NET 4.0, x86 et x64 - peut confirmer que l'exemple est toujours applicable. Merci! :) –

+1

C'est gentil!Je n'ai jamais su que le JIT fait ce genre d'optimisation et que volatile le désactive. – usr

+3

Juste pour être explicite, vous ajoutez le mot clé "volatile" à la déclaration de foo, n'est-ce pas? – JoeCool

21

Oui, cela dépend du matériel (il est peu probable que vous voyiez le problème sans plusieurs processeurs), mais cela dépend également de l'implémentation. Les spécifications de modèle de mémoire dans la spécification CLR permettent des choses que l'implémentation Microsoft du CLR ne fait pas nécessairement. La meilleure documentation que j'ai vu sur le mot-clé volatile est this blog post by Joe Duffy. Notez qu'il dit que la documentation MSDN est "très trompeuse".

+8

http://igoro.com/archive/volatile-keyword-in-c-memory-model-explained/ – Xaqron

+1

suivi par Joe Duffy sur mot-clé 'volatile'. http://www.bluebytesoftware.com/blog/2010/12/04/SayonaraVolatile.aspx –

6

Il ne s'agit pas vraiment d'une erreur qui se produit lorsque le mot clé 'volatile' n'est pas spécifié, plus qu'une erreur peut survenir quand elle n'a pas été spécifiée. En général, vous allez savoir quand c'est le cas mieux que le compilateur! La manière la plus simple de penser à cela serait que le compilateur puisse, s'il le voulait, aligner certaines valeurs. En marquant la valeur comme volatile, vous dites à vous-même et au compilateur que la valeur peut réellement changer (même si le compilateur ne le pense pas). Cela signifie que le compilateur ne doit pas aligner les valeurs, conserver le cache ou lire la valeur au début (dans le but d'optimiser).

Ce comportement n'est pas vraiment le même mot-clé qu'en C++.

MSDN a une description courte here. Voici un peut-être un plus en post profondeur sur les sujets de Volatility, Atomicity and Interlocking

4

Il est difficile de démontrer en C#, car le code est extrait par une machine virtuelle, donc sur une mise en œuvre de cette machine, il fonctionne bien sans volatile, alors que il pourrait échouer sur un autre.

The Wikipedia has a good example how to demonstrate it in C, though.

La même chose pourrait se produire en C# si le compilateur JIT décide que la valeur de la variable ne peut pas changer de toute façon et crée ainsi le code de la machine qui ne vérifie même pas plus longtemps. Si maintenant un autre thread changeait la valeur, votre premier thread pourrait toujours être pris dans la boucle.

Another example is Busy Waiting.

Encore une fois, cela pourrait se produire avec C# aussi bien, mais cela dépend fortement de la machine virtuelle et le compilateur JIT (ou d'un interprète, si elle n'a pas JIT ... en théorie, je pense que MS utilise toujours un compilateur JIT et Mono en utilise un, mais vous pouvez le désactiver manuellement).

4

Voilà ma contribution à la compréhension collective de ce comportement ... Ce n'est pas grand chose, juste une démonstration (basée sur la démonstration de xkip) qui montre le comportement d'un volatile vers une mémoire non volatile (par exemple « normal ») int valeur, côte à côte, dans le même programme ... qui est ce que je cherchais quand j'ai trouvé ce fil.

using System; 
using System.Threading; 

namespace VolatileTest 
{ 
    class VolatileTest 
    { 
    private volatile int _volatileInt; 
    public void Run() { 
     new Thread(delegate() { Thread.Sleep(500); _volatileInt = 1; }).Start(); 
     while (_volatileInt != 1) 
     ; // Do nothing 
     Console.WriteLine("_volatileInt="+_volatileInt); 
    } 
    } 

    class NormalTest 
    { 
    private int _normalInt; 
    public void Run() { 
     new Thread(delegate() { Thread.Sleep(500); _normalInt = 1; }).Start(); 
     // NOTE: Program hangs here in Release mode only (not Debug mode). 
     // See: http://stackoverflow.com/questions/133270/illustrating-usage-of-the-volatile-keyword-in-c-sharp 
     // for an explanation of why. The short answer is because the 
     // compiler optimisation caches _normalInt on a register, so 
     // it never re-reads the value of the _normalInt variable, so 
     // it never sees the modified value. Ergo: while (true)!!!! 
     while (_normalInt != 1) 
     ; // Do nothing 
     Console.WriteLine("_normalInt="+_normalInt); 
    } 
    } 

    class Program 
    { 
    static void Main() { 
#if DEBUG 
     Console.WriteLine("You must run this program in Release mode to reproduce the problem!"); 
#endif 
     new VolatileTest().Run(); 
     Console.WriteLine("This program will now hang!"); 
     new NormalTest().Run(); 
    } 

    } 
} 

Il y a quelques excellentes explications succinctes ci-dessus, ainsi que quelques bonnes références. Merci à tous de m'avoir aider à obtenir ma tête volatile (au moins assez pour savoir ne pas compter sur volatile où mon premier instinct était lock il).

Salutations, et merci pour TOUS les poissons. Keith


PS: Je serais très intéressé par une démonstration de la demande initiale, qui était: « Je voudrais voir un int volatile statique se comporter correctement lorsque un statique int. comporte mal

J'ai essayé et échoué à ce défi. (En fait, j'abandonnèrent assez rapidement ;-). Dans tout ce que j'ai essayé avec vars statique qu'ils se comportent « correctement », peu importe si oui ou non ils sont volatil ... et j'adorerais avoir une explication de POURQUOI c'est le cas, si c'est effectivement le cas ... Est-ce que le compilateur ne cache pas les valeurs des variables statiques dans les registres (c.-à-d. il met en cache une référence à cette adresse de tas à la place)?

Non, ce n'est pas une nouvelle question ... c'est une tentative d'appartenir à la communauté retour à la question d'origine.

2

Je suis tombé sur le texte suivant de Joe Albahari qui m'a beaucoup aidé.

J'ai attrapé un exemple du texte ci-dessus que je changeai un peu, en créant un champ volatile statique. Lorsque vous supprimez le mot clé volatile, le programme se bloque indéfiniment. Exécutez cet exemple en Relâchez le mode.

class Program 
{ 
    public static volatile bool complete = false; 

    private static void Main() 
    {   
     var t = new Thread(() => 
     { 
      bool toggle = false; 
      while (!complete) toggle = !toggle; 
     }); 

     t.Start(); 
     Thread.Sleep(1000); //let the other thread spin up 
     complete = true; 
     t.Join(); // Blocks indefinitely when you remove volatile 
    } 
}