2013-01-21 11 views
9

Penso che la maggior parte delle persone sono a conoscenza del problema seguente che avviene quando si costruisce in modalità di rilascio (codice preso dal Threading in C#):stranezza Volatile

static void Main() 
{ 
    bool complete = false; 

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

    t.Start(); 
    Thread.Sleep (1000); 

    complete = true; 
    t.Join();  // Blocks indefinitely 
} 

a causa di ottimizzazioni del compilatore cache il valore della complete e impedendo così il bambino thread da mai vedere il valore aggiornato.

Tuttavia, cambiando il codice di sopra di un bit:

class Wrapper 
{ 
    public bool Complete { get; set; } 
} 

class Test 
{ 
    Wrapper wrapper = new Wrapper(); 

    static void Main() 
    { 
     var test = new Test(); 
     var t = new Thread(() => 
     { 
      bool toggle = false; 
      while (!test.wrapper.Complete) toggle = !toggle; 
     }); 

     t.Start(); 
     Thread.Sleep(1000); 

     test.wrapper.Complete = true; 
     t.Join();  // Blocks indefinitely 
    } 
} 

rende il problema andare via (cioè il filo bambino è in grado di uscire dopo 1 secondo) senza l'uso di volatile, recinzioni memoria, o qualsiasi altra meccanismo che introduce una recinzione implicita.

In che modo l'incapsulamento aggiunto dell'indicatore di completamento influenza la sua visibilità tra i thread?

+1

Il tuo codice non è garantito per funzionare, ma non è garantito il fallimento. Non si deve fare affidamento sull'ottimizzazione del compilatore per la correttezza (o l'inesattezza in questo caso). – svick

+1

@svick: non lo sono davvero. Era solo qualcosa che ho notato accidentalmente. – Tudor

+1

possibile duplicato di [Un esempio riproducibile di utilizzo volatile] (http://stackoverflow.com/questions/6164466/a-reproducable-example-of-volatile-usage) –

risposta

6

Penso che tu abbia la risposta nella tua domanda:

a causa di ottimizzazioni del compilatore cache il valore della completa e impedendo così il filo bambino da mai vedere il valore aggiornato.

Le ottimizzazioni Compilatore/JIT vengono eseguite ogni volta che è logico/ritenuto sicuro e ragionevole da implementare. Quindi hai trovato casi in cui l'ottimizzazione non viene eseguita nel modo in cui ti aspetti che accada, forse c'è una buona ragione (qualcuno ha rilevato questo schema di utilizzo e l'ottimizzazione dei blocchi) o semplicemente non è stato ottimizzato (molto probabilmente).

+5

Il punto chiave è che si tratta di un comportamento non definito. Il team del compilatore potrebbe decidere domani di ottimizzare le proprietà allo stesso modo del primo esempio, oppure non potrebbero ottimizzare il primo (o farlo in modo diverso) in modo che non si fermi. – Servy

+0

Sono d'accordo con il tuo punto e molto probabilmente questo è il caso. Sto solo cercando di scoprire se c'è un pattern che posso individuare per determinare se un codice come questo fallirà o meno. – Tudor

0

Il primo caso è una semplice propagazione di alias su una variabile locale. I compilatori lo fanno in modo abbastanza aggressivo (http://en.m.wikipedia.org/wiki/Aliasing_(computing)) Il secondo caso, anche se sembra lo stesso a causa della sintattica somiglianza delle proprietà con le variabili membro, ha chiamate di metodo (getter/setter) e non può essere semplicemente semplificato.

+0

nel primo caso, non è una semplice impostazione di valore .. il runtime non può semplicemente "ignorare" qualcosa del genere –

+0

non capisco cosa intendi con "non è una semplice impostazione di valore". La propagazione degli alias è una tecnica di compilazione per semplificare o sostituire le variabili http://en.wikipedia.org/wiki/Aliasing_(computing) – nakhli