Domanda

Vorrei codificare un piccolo programma che illustra visivamente il comportamento della parola chiave volatile . Idealmente, dovrebbe essere un programma che esegue l'accesso simultaneo a un campo statico non volatile e che ottiene un comportamento errato a causa di ciò.

L'aggiunta della parola chiave volatile nello stesso programma dovrebbe risolvere il problema.

Quel qualcosa che non sono riuscito a raggiungere. Anche provando più volte, abilitando l'ottimizzazione, ecc., Ottengo sempre un comportamento corretto senza la parola chiave "volatile".

Hai qualche idea su questo argomento? Sai come simulare un problema del genere in una semplice app demo? Dipende dall'hardware?

È stato utile?

Soluzione

Ho realizzato un esempio funzionante!

L'idea principale è stata ricevuta da wiki, ma con alcune modifiche per C #. L'articolo wiki lo dimostra per il campo statico di C ++, sembra che C # compili sempre attentamente le richieste in campi statici ... e ne faccio un esempio con uno non statico:

Se esegui questo esempio in Rilascio e senza debugger (ovvero utilizzando Ctrl + F5), allora la riga while (test.foo! = 255) sarà ottimizzato su 'while (true)' e questo programma non ritorna mai. Ma dopo aver aggiunto la parola chiave volatile , ottieni 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");
    }
}

Altri suggerimenti

Sì, dipende dall'hardware (è improbabile che tu veda il problema senza più processori), ma dipende anche dall'implementazione. Le specifiche del modello di memoria nelle specifiche CLR consentono cose che l'implementazione Microsoft del CLR non necessariamente fa. La migliore documentazione che ho visto sulla parola chiave volatile è questo post sul blog di Joe Duffy . Nota che dice che la documentazione MSDN è "fuorviante."

In realtà non si tratta di un errore che si verifica quando non viene specificata la parola chiave "volatile", più che un errore potrebbe verificarsi quando non è stato specificato. Generalmente saprai quando questo è il caso migliore del compilatore!

Il modo più semplice di pensarci è che il compilatore potrebbe, se lo desidera, incorporare determinati valori. Contrassegnando il valore come volatile, stai dicendo a te stesso e al compilatore che il valore può effettivamente cambiare (anche se il compilatore non la pensa così). Ciò significa che il compilatore non dovrebbe valori in linea, mantenere la cache o leggere il valore in anticipo (nel tentativo di ottimizzare).

Questo comportamento non è in realtà la stessa parola chiave di C ++.

MSDN ha una breve descrizione qui . Ecco forse un post più approfondito sugli argomenti di Volatilità, atomicità e interblocco

È difficile dimostrarlo in C #, poiché il codice viene estratto da una macchina virtuale, quindi su un'implementazione di questa macchina funziona perfettamente senza volatilità, mentre potrebbe fallire su un'altra.

Wikipedia ha un buon esempio su come dimostrarlo in C, però.

La stessa cosa potrebbe accadere in C # se il compilatore JIT decidesse che il valore della variabile non può comunque cambiare e quindi crea un codice macchina che non lo controlla più. Se ora un altro thread stava modificando il valore, il tuo primo thread potrebbe ancora essere catturato nel ciclo.

Un altro esempio è Occupato in attesa.

Ancora una volta, questo potrebbe accadere anche con C #, ma dipende fortemente dalla macchina virtuale e dal compilatore JIT (o interprete, se non ha JIT ... in teoria, penso che MS usi sempre un compilatore JIT e anche Mono ne usa uno, ma potresti essere in grado di disabilitarlo manualmente).

Ecco il mio contributo alla comprensione collettiva di questo comportamento ... Non è molto, solo una dimostrazione (basata sulla demo di xkip) che mostra il comportamento di un versetto volatile con un valore int non volatile (cioè "normale") , fianco a fianco, nello stesso programma ... che è quello che stavo cercando quando ho trovato questa discussione.

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 alcune eccellenti spiegazioni succinte sopra, così come alcuni grandi riferimenti. Grazie a tutti per avermi aiutato a capire volatile (almeno abbastanza per sapere di non fare affidamento su volatile dove il mio primo istinto era lock ) .

Saluti e grazie per TUTTO il pesce. Keith.


PS: Sarei molto interessato a una demo della richiesta originale, che era: " Mi piacerebbe vedere un statico volatile int si comporta correttamente quando un statico int si comporta male.

Ho provato e fallito questa sfida. (In realtà mi sono arreso abbastanza rapidamente ;-). In tutto ciò che ho provato con variabili statiche si comportano "correttamente" indipendentemente dal fatto che siano volatili ... e mi piacerebbe una spiegazione del PERCHÉ che è il caso, se davvero è il caso ... È che il compilatore non lo fa? memorizzare nella cache i valori delle variabili statiche nei registri (ovvero memorizza nella cache un riferimento a quell'indirizzo heap invece)?

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

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

Ho preso un esempio dal testo sopra che ho modificato un po ', creando un campo volatile statico. Quando rimuovi la parola chiave volatile il programma si bloccherà indefinitamente. Esegui questo esempio in Rilascio .

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
    }
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top