2010-11-24 15 views
9

per implementare un serratura di codice libero per il multithreading applicazione che ho usato volatile variabili, Teoricamente: Il volatile parola chiave viene utilizzata semplicemente per fare in modo che tutte le discussioni vedono il valore più aggiornata di un variabile volatile; quindi se il thread A aggiorna il valore della variabile e il thread B legge quella variabile subito dopo l'aggiornamento verrà visualizzato il valore più aggiornato che è stato scritto di recente dal thread A. Come ho letto in un C# 4.0 in poche parole book che questo è corretto perchéVolatile e Thread.MemoryBarrier in C#

applicando volatili non impedisce una scrittura seguita da una lettura di essere scambiati.

Potrebbe questo problema essendo risolto mettendo Thread.MemoryBarrier() prima di ogni get della variabile volatile come:

private volatile bool _foo = false; 

private void A() 
{ 
    //… 
    Thread.MemoryBarrier(); 
    if (_foo) 
    { 
     //do somthing 
    } 
} 

private void B() 
{ 
    //… 
    _foo = true; 
    //… 
} 

E se questo risolve il problema; considera che abbiamo un ciclo while che dipende da quel valore in una delle sue condizioni; sta mettendo Thread.MemoryBarrier() prima del ciclo while è un modo corretto per risolvere il problema? Esempio:

private void A() 
{ 
    Thread.MemoryBarrier(); 
    while (_someOtherConditions && _foo) 
    { 
     // do somthing. 
    } 
} 

Per essere più precisi voglio la variabile _foo di dare il suo valore più fresco quando una qualsiasi discussione per chiedere in qualsiasi momento; quindi se l'inserimento Thread.MemoryBarrier() prima di chiamare la variabile risolve il problema, allora potrei usare Foo proprietà invece di _foo e fare un Thread.MemoryBarrier() all'interno del get di tale proprietà come:

Foo 
{ 
    get 
    { 
     Thread.MemoryBarrier(); 
     return _foo; 
    } 
    set 
    { 
     _foo = value; 
    } 
} 
+0

Possibile duplicato ... http: //stackoverflow.com/questions/1330590/when-to-use-volatile-or-thread-memorybarrier-in-threadsafe-locking-code-c –

+0

@Aron; no non è duplicato; questa è un'altra domanda. –

+0

@Jalal Stai cercando di risolvere un problema utilizzando questo approccio volatile/memorybarrier o stai bene con qualsiasi approccio che risolva il tuo problema? –

risposta

8

"C# In a Nutshell" è corretto, ma la sua affermazione è discutibile. Perché?

  • A 'scrittura' seguita da una 'lettura', withot 'volatili', è garantito a verificarsi in programma ordine comunque se effettua logica all'interno di un singolo filo
  • Il 'write' prima di un 'leggi' in un programma multi-thread è completamente inutile di cui preoccuparsi nell'esempio.

Facciamo chiarezza. Prendete il vostro codice originale:

private void A() 
{ 
    //… 
    if (_foo) 
    { 
     //do something 
    } 
} 

Cosa succede se lo scheduler thread è già controllato la variabile _foo, ma viene sospesa poco prima che il commento //do something? Bene, a quel punto l'altro thread potrebbe cambiare il valore di _foo, il che significa che tutti i tuoi volatili e Thread.MemoryBarriers non hanno contato nulla !!! Se è assolutamente necessario evitare do_something se il valore di _foo è falso, non è possibile scegliere un blocco.

Tuttavia, se è corretto eseguire do something quando improvvisamente _foo diventa falso, significa che la parola chiave volatile è più che sufficiente per le proprie esigenze.

Per essere chiari: tutti i soccorritori che stanno dicendo di utilizzare una barriera di memoria non sono corretti o forniscono eccessivo.

+0

grazie per la tua risposta; Nel mio caso voglio evitare che do_qualcosa venga eseguito se _foo fosse vero il più possibile, ma è ** non mortalmente critico **; ma se potessi renderlo più accurato con MemoryBarrier allora ** perché non ** usarlo con il volatile .. Se il programma di thread sospende il thread dopo il controllo di _foo "witch è possibile" allora il do_somthing verrà eseguito ma almeno noi ** riduciamo le possibilità ** che il _foo sia falso in quel momento giusto? –

+2

@Jalal: la dichiarazione 'volatile' garantisce già, da sola, che la variabile non verrà memorizzata in un registro CPU. Garantisce letture e scritture di memoria diretta (che invalida la cache di altri processori su una macchina multiprocessore). Pertanto la barriera esplicita della memoria non è necessaria; il 'volatile' sta già realizzando ciò di cui hai bisogno. –

+0

@BrentArias Il MemoryBarrier * è * necessario perché è possibile che il processore aggiorni il valore su un processore e non sull'altro, quindi un thread legge un valore aggiornato e l'altro thread legge un valore scaduto. – Anthony

0

Nel vostro secondo esempio, si avrebbe bisogno di mettere anche a Thread.MemoryBarrier(); all'interno del ciclo, per assicurarsi di ottenere il valore più recente ogni volta che si verifica la condizione del ciclo.

+0

se questo risolve il problema, allora mettere Thread.MemoryBarrier() dovrebbe essere sull'ultima riga prima della parentesi chiusa del ciclo while e prima della chiamata del ciclo while. –

+0

@Jalal: Sono abbastanza sicuro che l'esplicito 'MemoryBarrier 'le chiamate non sono richieste in questi esempi, a condizione che' _foo' sia contrassegnato come 'volatile'. (Non sono un esperto, probabilmente userò un 'lock'.) – LukeH

+2

In questo caso: l'uso di Thread.MemoryBarrier, sia prima che dopo, o entrambi, non sta raggiungendo nulla di volatile che non stava già raggiungendo . –

0

Tirato da here ...

class Foo 
{ 
    int _answer; 
    bool _complete; 

    void A() 
    { 
    _answer = 123; 
    Thread.MemoryBarrier(); // Barrier 1 
    _complete = true; 
    Thread.MemoryBarrier(); // Barrier 2 
    } 

    void B() 
    { 
    Thread.MemoryBarrier(); // Barrier 3 
    if (_complete) 
    { 
     Thread.MemoryBarrier();  // Barrier 4 
     Console.WriteLine (_answer); 
    } 
    } 
} 

Barriere 1 e 4 prevenire questo esempio da scrivere “0”. Le barriere 2 e 3 forniscono una garanzia di freschezza: esse assicurano che se B corri dopo A, la lettura di _complete valga su true.

Quindi, se torniamo al vostro esempio looping ... questo è come dovrebbe apparire ...

parole
private void A() 
{ 
    Thread.MemoryBarrier(); 
    while (_someOtherConditions && _foo) 
    { 
     //do somthing 
     Thread.MemoryBarrier(); 
    } 
} 
+1

@ Aaron Grazie Controllo questo link prima che sia nello stesso libro "C# 4.0 in poche parole" .. tuttavia se questo risolve il problema, allora mettere Thread.MemoryBarrier() dovrebbe essere sull'ultima riga prima del parentesi chiusa del ciclo while . –

+0

@Jalal Dipende da cosa fa il "fai qualcosa", dove il ThreadBarrier si trova ora influenza le tue _someOtherconditions/_foo vars –

+1

@Aaron: Se '_foo' è contrassegnato come' volatile ', sono abbastanza sicuro che l'esplicito 'MemoryBarrier' le chiamate non sono necessarie in questi particolari esempi. (Piuttosto sicuro, ma non sicuro al 100%, e in caso di dubbio probabilmente userò semplicemente un 'lock'.) – LukeH

0

di Microsoft su barriere di memoria:

MemoryBarrier è richiesto solo su sistemi multiprocessore con ordini di memoria deboli (ad esempio, un sistema che utilizza più processori Intel Itanium).

Per la maggior parte degli scopi, l'istruzione C# lock, l'istruzione SyncLock di Visual Basic o la classe Monitor forniscono modi più semplici per sincronizzare i dati.

+0

Fare riferimento a http://www.albahari.com/threading/part4.aspx per verificare che le parole di Microsoft non siano corrette !!. –

+0

Il poster chiede in particolare un'implementazione senza blocco. –

5

Il libro è corretto.
Il modello di memoria CLR indica che le operazioni di caricamento e memorizzazione possono essere riordinate. Questo vale per le variabili volatili e non volatile.

Dichiarazione di una variabile come volatile significa soltanto che le operazioni di carico avranno acquisiscono semantica, e memorizzare le operazioni avranno rilascio semantica. Inoltre, il compilatore eviterà di eseguire determinate ottimizzazioni che si relazionano sul fatto che la variabile viene acceduta in modo serializzato, a thread singolo (ad esempio carico/archivi di sollevamento fuori dai loop).

L'utilizzo della sola parola chiave volatile non crea sezioni critiche e non provoca la sincronizzazione magica dei thread tra loro.

Si dovrebbe essere estremamente attento quando si scrive codice di blocco libero. Non c'è niente di semplice in questo, e anche gli esperti hanno difficoltà a farlo bene.
Qualunque sia il problema originale che stai cercando di risolvere, è probabile che ci sia un modo molto più ragionevole per farlo.

+0

@Liran: Sapevo che l'utilizzo della sola volatile non creava sezioni critiche né causava la sincronizzazione dei thread tra loro. Sto solo chiedendo che la variabile _foo sia un valore aggiornato quando un thread lo richiede in qualsiasi momento all'interno del codice di blocco libero. –

+0

@Jalal, dichiarando che la variabile come volatile garantirà che ogni operazione di caricamento leggerà il valore più aggiornato della variabile (ad esempio dalla memoria principale anziché da un registro). Non dovresti diffondere barriere di memoria su tutto il tuo codice ... così facendo potresti avere un notevole effetto negativo sulle prestazioni. Di nuovo, se non stai solo cercando di giocare e sperimentare con il codice di blocco, e c'è un codice di produzione reale nell'immagine ... ti incoraggio a trovare un'altra soluzione. – Liran

+0

@Liran; "dichiarare la variabile come volatile garantirà che ogni operazione di caricamento leggerà il valore più aggiornato della variabile"; sarà come ho capito dal libro http://www.albahari.com/threading/part4.aspx che wirte seguito da read potrebbe non ottenere il valore più aggiornato, e sì, sto provando a sperimentare con il lock free code; la domanda è: è un grande effetto negativo sulle prestazioni dell'applicazione quando si esegue un 'Thread.MemoryBarrier' prima di qualsiasi operazione di ottenere la variabile volatile, se non potrei metterla in una proprietà prima di ottenere qualsiasi variabile.si prega di consultare l'aggiornamento della domanda –

Problemi correlati