2009-08-25 9 views

risposta

20

Si utilizza volatile/Thread.MemoryBarrier() quando si desidera accedere a una variabile attraverso thread senza bloccare.

Le variabili che sono atomiche, ad esempio int, vengono sempre lette e scritte interamente insieme. Ciò significa che non otterrai mai metà del valore prima che un altro thread lo cambi e l'altra metà dopo che è stata modificata. Per questo puoi tranquillamente leggere e scrivere il valore in diversi thread senza syncronising.

Tuttavia, il compilatore può ottimizzare alcune letture e scritture, che vengono impedite con la parola chiave volatile.Se ad esempio avere un ciclo come questo:

sum = 0; 
foreach (int value in list) { 
    sum += value; 
} 

Il compilatore può effettivamente fare i calcoli in un registro del processore e solo scrivere il valore della variabile sum dopo il ciclo. Se si crea la variabile sumvolatile, il compilatore genererà il codice che legge e scrive la variabile per ogni modifica, in modo che il suo valore sia aggiornato in tutto il ciclo.

+1

+1, ma in quali scenari è importante che il compilatore generi codice che legge e scrive la variabile per ogni modifica? – dtb

+0

Ciò è maggiormente desiderato quando si inserisce dtb, se non un thread può ottenere un valore diverso da quello dell'altro e possono accadere tutti i tipi di cose pazzesche. – Skurmedel

+0

Bene, quello o usare una barriera di memoria. – Skurmedel

10

Cosa c'è di sbagliato con

private static readonly object syncObj = new object(); 
private static int counter; 

public static int NextValue() 
{ 
    lock (syncObj) 
    { 
     return counter++; 
    } 
} 

?

Questo fa tutto il necessario blocco, barriere di memoria, ecc. Per voi. È ben compreso e più leggibile di qualsiasi codice di sincronizzazione personalizzato basato su volatile e Thread.MemoryBarrier().


EDIT

non riesco a pensare ad uno scenario in cui mi piacerebbe utilizzare volatile o Thread.MemoryBarrier(). Ad esempio

private static volatile int counter; 

public static int NextValue() 
{ 
    return counter++; 
} 

è non equivalente al codice sopra ed è non thread-safe (volatile non opera ++ magicamente diventare thread-safe).

In un caso come questo:

private static volatile bool done; 

void Thread1() 
{ 
    while (!done) 
    { 
     // do work 
    } 
} 

void Thread2() 
{ 
    // do work 
    done = true; 
} 

(che dovrebbe funzionare) userei un ManualResetEvent per segnalare quando Thread2 è fatto.

+0

Niente di "sbagliato" nel codice. Sto cercando di capire le circostanze in cui si applicano volatili e MemoryBarrier. Come nella risposta di Jon Skeet qui: http://stackoverflow.com/questions/395232/we-need-to-lock-a-net-int32-when-reading-it-in-a-multithreaded-code. – Alex

+0

.net ha anche un modello di memoria abbastanza rigido, tale lettura o scrittura non può seguire un'altra scrittura. –

+4

Anche se le specifiche ECMA hanno un modello di memoria abbastanza debole, quindi si potrebbe voler fare attenzione, vedere http://www.bluebytesoftware.com/blog/2007/11/10/CLR20MemoryModel.aspx e http: // blog .msdn.com/cbrumme/archive/2003/05/17/51445.aspx –

10

In sostanza se si utilizza qualsiasi altro tipo di sincronizzazione per rendere il proprio codice non protetto, non è necessario.

La maggior parte dei meccanismi di blocco (incluso il blocco) implica automaticamente una barriera di memoria in modo che più processori possano ottenere le informazioni corrette.

Volatile e MemoryBarrier vengono principalmente utilizzati in scenari senza blocco in cui si tenta di evitare la penalizzazione delle prestazioni del blocco.

Edit: Si consiglia di leggere this article da Joe Duffy circa il modello di memoria CLR 2.0, chiarisce un sacco di cose (se siete veramente interessati vi consigliamo di leggere tutto l'articolo da Joe Duffie che è di grandi dimensioni il più esperto in parallelismo in .NET)

+0

+1, ma solo così è assolutamente chiaro. Se fai ciò che fa dtb, volatile/Thread.MemoryBarrier non è necessario. Ci sono anche situazioni in cui la volatilità da sola su un membro non è sufficiente. – Skurmedel

0

Poiché il nome implica volatile garantisce che il valore della cache venga svuotato in memoria in modo che tutti i thread vedano lo stesso valore. Ad esempio, se ho un numero intero la cui ultima scrittura è salvata nella cache, altri thread potrebbero non vederlo. Potrebbero persino vedere la loro copia cache di quell'intero. Marcare una variabile come volatile lo fa leggere direttamente dalla memoria.

Sriwantha Sri Aravinda Attanayake

Problemi correlati