Illustrando l’utilizzo della parola chiave volatile in C#

Vorrei il codice di un piccolo programma che illustra visivamente il comportamento del volatile parola chiave. Idealmente, dovrebbe essere un programma che effettua l’accesso simultaneo a non volatile campo statico e che ottiene comportamento non corretto a causa di questo.

Aggiungendo la parola chiave volatile nello stesso programma dovrebbe risolvere il problema.

Che qualcosa che non riescono a raggiungere. Anche provando più volte, con la conseguente ottimizzazione, ecc…. Mi sento sempre un comportamento corretto, senza il ‘volatile’ la parola chiave.

Avete alcuna idea su questo argomento? Sapete come simulare un problema in una semplice demo app? Dipende da hardware?

 

6 Replies
  1. 101

    Ho realizzato un esempio funzionante!

    L’idea principale che ha ricevuto da wiki, ma con alcune modifiche per C#. Il wiki articolo viene illustrato questo per campo statico di C++, è simile a C# sempre con cura la compilazione delle richieste per i campi statici… e faccio esempio non statico:

    Se si esegue questo esempio in Rilascio modalità e senza debugger (cioè con Ctrl+F5) e poi la linea while (test.foo != 255) sarà ottimizzato per ‘while(true)’ e questo programma non restituisce mai.
    Ma dopo l’aggiunta di volatile parola chiave, si ottiene sempre “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");
        }
    }
    • Testato su .NET 4.0, sia x86 e x64 – può confermare che l’esempio è ancora applicabile. Grazie! 🙂
    • Che è bello! Non ho mai saputo che il JIT fa questo tipo di ottimizzazione e che la volatilità disabilita.
    • Solo per essere più espliciti, si sta aggiungendo il “volatile” e la parola chiave di pippo dichiarazione, giusto?
    • sì, lo è.
  2. 21

    Sì, è dipendente dall’hardware (è improbabile vedere il problema, senza più processori), ma è anche l’implementazione dipendente. Il modello di memoria specifiche in CLR spec permesso di cose che l’implementazione Microsoft di CLR non necessariamente fare. La migliore documentazione ho visto sul volatile parola chiave è questo post sul blog di Joe Duffy. Nota che dice la documentazione di MSDN è “altamente fuorviante.”

  3. 6

    Non è davvero una questione di guasto succede quando l’ ‘volatile’ la parola chiave non è specificato, più che un errore può succedere quando non è stato specificato. In genere si sta andando a sapere quando questo è il caso migliore di quello che il compilatore!

    Il modo più semplice di pensare sarebbe che il compilatore potrebbe, se lo volesse, che in linea di determinati valori. Segnando il valore come volatile, si sta dicendo a te stesso e al compilatore che il valore può realmente cambiare (anche se il compilatore non la pensa così). Questo significa che il compilatore non dovrebbe in linea di valori, di conservare la cache o leggere il valore di primi (nel tentativo di ottimizzare).

    Questo comportamento non è proprio la stessa parola chiave, come in C++.

    MSDN è una breve descrizione qui.
    Qui è forse più in profondità post su argomenti di La volatilità, Atomicità e ad Incastro

  4. 4

    È difficile dimostrare in C#, come il codice viene estratto da una macchina virtuale, così, da un implementazione di questa macchina è lavorare senza volatile, mentre potrebbe non riuscire in un altro.

    Wikipedia è un buon esempio di come a dimostrare in C, però.

    La stessa cosa potrebbe accadere in C#, se il compilatore JIT decide che il valore della variabile non può essere modificata in ogni caso e, quindi, crea il codice macchina che non ha nemmeno controllare di più. Se ora l’altro thread è stato modificato il valore, il tuo primo thread potrebbe ancora essere catturati nel ciclo.

    Un altro esempio è Occupato in Attesa.

    Di nuovo, questo potrebbe accadere con C#, ma dipende fortemente dalla macchina virtuale e il compilatore JIT (o di un interprete, se non ha JIT… in teoria, penso che MS utilizza sempre un compilatore JIT e anche Mono utilizza uno; ma si potrebbe essere in grado di disattivare manualmente).

  5. 4

    Ecco il mio contributo per la comprensione collettiva di questo comportamento… non È molto, solo una dimostrazione (basato su xkip demo) che mostra il comportamento di un volatile versi non volatile (cioè “normale”) valore di tipo int, side-by-side, nello stesso programma… che è quello che cercavo quando ho trovato questo thread.

    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();
        }
    
      }
    }

    Ci sono alcuni davvero eccellenti succinte spiegazioni di cui sopra, così come alcuni grandi riferimenti. Grazie a tutti per avermi aiutato a ottenere la mia testa intorno volatile (almeno abbastanza per sapere che non si basano su volatile mio primo istinto è stato lock it).

    Saluti, e Grazie per TUTTO il pesce. Keith.


    PS: sarei molto interessato ad una demo della richiesta originale, che era: “mi piacerebbe vedere un statico volatile int comportarsi correttamente dove un statico int si comporta male.

    Ho provato e fallito in questa sfida. (In realtà ho dato abbastanza in fretta ;-). In tutto ciò ho provato con static var loro comportamento “corretto” indipendentemente dal fatto che o non sono volatile … e mi piacerebbe una spiegazione del PERCHÉ questo è il caso, se davvero è il caso… È che il compilatore non memorizzare nella cache il valori di static var in registri (cioè la cache di un di riferimento per che heap-indirizzo)?

    No, questa non è una nuova domanda… è un tentativo di stear comunità indietro alla domanda originale.

  6. 2

    Mi sono imbattuto nel seguente testo da Joe Albahari che mi ha aiutato molto.

    Ho preso un esempio tratto dal testo di cui sopra, che ho modificato un po’, con la creazione di una statico campo volatile. Quando si rimuove il volatile parola chiave il programma blocco a tempo indeterminato. Eseguire questo esempio in Rilascio modalità.

    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
        }
    }

Lascia un commento