2010-05-16 15 views
12

Ho letto di recente sulle barriere della memoria e sul problema del riordino e ora ho una certa confusione a riguardo.Memory Barrier per lock statement

Si consideri il seguente scenario:

private object _object1 = null;  
private object _object2 = null; 
private bool _usingObject1 = false; 

private object MyObject 
{ 
    get 
    { 
     if (_usingObject1) 
     { 
      return _object1; 
     } 
     else 
     { 
      return _object2; 
     } 
    } 
    set 
    { 
     if (_usingObject1) 
     { 
      _object1 = value; 
     } 
     else 
     { 
      _object2 = value; 
     } 
    } 
} 

private void Update() 
{ 
    _usingMethod1 = true; 
    SomeProperty = FooMethod(); 
    //.. 
    _usingMethod1 = false; 
} 
  1. A Update metodo; è l'istruzione _usingMethod1 = true eseguita sempre prima di ottenere o impostare la proprietà? o a causa di un problema di riordino, non possiamo garantirlo?

  2. dovremmo usare volatile come

    private volatile bool _usingMethod1 = false; 
    
  3. Se usiamo lock; possiamo garantire quindi ogni affermazione all'interno della serratura saranno eseguiti in modo simile:

    private void FooMethod() 
    { 
        object locker = new object(); 
        lock (locker) 
        { 
         x = 1; 
         y = a; 
         i++; 
        } 
    } 
    

risposta

28

Il tema delle barriere della memoria è piuttosto complesso. Di tanto in tanto, incassa persino gli esperti. Quando parliamo di una barriera di memoria, stiamo davvero combinando due idee diverse.

  • Acquire recinzione: Una barriera memoria nella quale altra legge & scritture non sono autorizzati a muoversi prima la recinzione.
  • Recinzione: una barriera di memoria in cui altre scritture & non possono essere spostate dopo il.

Una barriera di memoria che crea solo uno dei due è talvolta chiamato un mezza recinzione. Una barriera di memoria che crea entrambi è talvolta chiamata full-fence.

La parola chiave volatile crea semirette. Le letture di campi volatili hanno acquisito semantica mentre le scritture hanno una semantica di rilascio. Ciò significa che nessuna istruzione può essere spostata prima di una lettura o dopo una scrittura.

La parola chiave lock crea reti fisse su entrambi i limiti (entrata e uscita). Ciò significa che nessuna istruzione può essere spostata prima o dopo ciascun limite.

Tuttavia, tutto questo moot se ci interessa solo un thread. L'ordine, così come viene percepito da quel thread, è sempre preservato.In realtà, senza quella garanzia fondamentale nessun programma avrebbe mai funzionato correttamente. Il vero problema è con il modo in cui altri thread percepiscono letture e scritture. È qui che devi essere preoccupato.

Quindi, per rispondere alle vostre domande:

  1. Dal punto di vista di un singolo filo ... sì. Dalla prospettiva di un altro thread ... no.

  2. Dipende. Potrebbe funzionare, ma ho bisogno di avere una migliore comprensione di ciò che stai cercando di ottenere.

  3. Dal punto di vista di un altro thread ... no. Le letture e le scritture sono libere di muoversi all'interno dei limiti della serratura. Non riescono a muoversi fuori da questi limiti. Questo è il motivo per cui è importante che anche altri thread creino barriere di memoria.

+0

Grazie per l'informazione, mi aiuta davvero a capire meglio il concetto. Quello che mi serve per essere sicuro è che l'istruzione "_usingMethod1 = true" verrà eseguita sempre prima dell'istruzione next SomeProperty = FooMethod(); Nel multithread senario come realizzarlo? è di: _usingMethod1 = true; Thread.MemoryBarrier(); SomeProperty = FooMethod(); o blocca per recinti pieni quindi non riordinare: lock (locker) {_usingMethod1 = true; } SomeProperty = FooMethod(); o forse semplicemente rendendo _usingMethod1 una variabile volatile. Grazie per l'aiuto. –

+2

Vorrei avvolgere l'intero contenuto del metodo di aggiornamento in un blocco. Oltre alle barriere della memoria, garantisce anche l'atomicità, che è altrettanto importante. Inoltre, questi idiomi lock-free (via volatile, Thread.MemoryBarrier, ecc.) Sono incredibilmente difficili da ottenere. –

4

La volatilità parola chiave non realizza nulla qui. Ha garanzie molto deboli, non implica una barriera di memoria. Il tuo codice non mostra la creazione di un altro thread, quindi è difficile indovinare se è necessario il blocco. È tuttavia un requisito irrinunciabile se due thread possono eseguire Update() allo stesso tempo e utilizzare lo stesso oggetto.

Attenzione che il codice di protezione inviato non blocca nulla. Ogni thread avrebbe la propria istanza dell'oggetto "locker". Devi renderlo un campo privato della tua classe, creato dal costruttore o da un inizializzatore. Quindi:

private object locker = new object(); 

private void Update() 
{ 
    lock (locker) 
    { 
     _usingMethod1 = true; 
     SomeProperty = FooMethod(); 
     //.. 
     _usingMethod1 = false; 
    } 
} 

Si noti che ci sarà anche una gara nell'assegnazione SomeProperty.

+0

Il volatile ha una barriera di memoria; e così ho chiesto se la barriera di momory eliminasse il reordaring in modo che _usingMethod1 = true garantisse sempre l'excute prima di ottenere o impostare la proprietà SomeProperty, Fornisco un lock solo per la barriera memoro non per il problema di sincronizzazione con altri thread e così Lo faccio diventare una variabile locale all'interno del metodo, perché stavo chiedendo se eviterebbe il riordino delle istruzioni all'interno della serratura. –

+1

Un singolo thread * sempre * ha una vista coerente delle variabili che utilizza. I programmi non potevano funzionare se così non fosse. –