Illustrer l'utilisation du mot-clé volatile en C #

88

Je voudrais coder un petit programme qui illustre visuellement le comportement du volatilemot - clé. Idéalement, ce devrait être un programme qui effectue un accès simultané à un champ statique non volatil et qui obtient un comportement incorrect à cause de cela.

L'ajout du mot-clé volatile dans le même programme devrait résoudre le problème.

C'est quelque chose que je n'ai pas réussi à réaliser. Même en essayant plusieurs fois, en activant 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 simple application de démonstration? Cela dépend-il du matériel?

Romain Verdier
la source

Réponses:

102

J'ai réalisé un exemple de travail!

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

Si vous exécutez cet exemple en mode Release et sans débogueur (c'est-à-dire en utilisant Ctrl + F5) alors la ligne while (test.foo != 255)sera optimisée à «while (true)» et ce programme ne retournera jamais. Mais après avoir ajouté un volatilemot-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");
    }
}
S.Serpooshan
la source
5
Testé sur .NET 4.0, à la fois x86 et x64 - peut confirmer que l'exemple est toujours applicable. Merci! :)
Roman Starkov
1
C'est bien! Je n'ai jamais su que le JIT faisait ce genre d'optimisation et que volatile le désactive.
usr
3
Pour être explicite, vous ajoutez le mot-clé «volatile» à la déclaration de toto, non?
JoeCool
21

Oui, cela dépend du matériel (vous ne verrez probablement pas le problème sans plusieurs processeurs), mais cela dépend également de l'implémentation. Les spécifications du 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.

Craig Stuntz
la source
6

Ce n'est pas vraiment une question de faute qui se produit lorsque le mot-clé 'volatile' n'est pas spécifié, plus qu'une erreur peut se produire lorsqu'elle n'a pas été spécifiée. En général, vous saurez quand c'est le cas mieux que le compilateur!

La façon la plus simple d'y penser serait que le compilateur puisse, s'il le voulait, incorporer certaines valeurs. En marquant la valeur comme volatile, vous vous dites, ainsi qu'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, garder le cache ou lire la valeur tôt (dans une tentative d'optimisation).

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

MSDN a une brève description ici . Voici un article peut-être plus approfondi sur les sujets de la volatilité, de l'atomicité et de l'imbrication

Ray Hayes
la source
4

C'est difficile à démontrer en C #, car le code est abstrait par une machine virtuelle, donc sur une implémentation de cette machine, il fonctionne correctement sans volatil, alors qu'il peut échouer sur une autre.

Wikipédia a cependant un bon exemple pour le démontrer en C.

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 un code machine qui ne la vérifie même plus. Si maintenant un autre thread modifiait la valeur, votre premier thread pourrait encore être pris dans la boucle.

Un autre exemple est occupé en attente.

Encore une fois, cela pourrait également arriver avec C #, mais cela dépend fortement de la machine virtuelle et du compilateur JIT (ou de l'interpréteur, s'il n'a pas de JIT ... en théorie, je pense que MS utilise toujours un compilateur JIT et aussi Mono utilise one; mais vous pourrez peut-être le désactiver manuellement).

Mecki
la source
4

Voici ma contribution à la compréhension collective de ce comportement ... Ce n'est pas grand chose, juste une démonstration (basée sur la démo de xkip) qui montre le comportement d'un volatile vers une valeur int non volatile (ie "normale"), côte à côte -côté, dans le même programme ... c'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 aidé à comprendre volatile(au moins assez pour savoir ne pas se fier à volatilemon premier instinct lock).

Bravo et merci pour TOUS les poissons. Keith.


PS: Je serais très intéressé par une démo de la requête originale, qui était: "Je voudrais voir un int statique volatile se comporter correctement là où un int statique se comporte mal.

J'ai essayé et échoué ce défi. (En fait j'ai abandonné assez rapidement ;-). Dans tout ce que j'ai essayé avec des variables statiques, ils se comportent "correctement", qu'ils soient volatils ou non ... et j'aimerais une explication de POURQUOI c'est le cas, si c'est effectivement le cas ... le compilateur ne met pas en cache les valeurs des variables statiques dans les registres (c'est-à-dire qu'il met en cache une référence à cette adresse de tas à la place)?

Non , ce n'est pas une nouvelle question ... il est une tentative de la communauté TSAR revenir à la question initiale.

corlettk
la source
2

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

J'ai pris un exemple du texte ci-dessus que j'ai un peu modifié, en créant un champ volatil statique. Lorsque vous supprimez le volatilemot - clé, le programme se bloque indéfiniment. Exécutez cet exemple en mode Release .

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
    }
}
Martijn B
la source