31

C# 4 in sintesi (altamente consigliato btw) utilizza il seguente codice per dimostrare il concetto di MemoryBarrier (supponendo A e B sono stati eseguiti su diversi thread):Perché ho bisogno di una barriera di memoria?

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); 
    } 
    } 
} 

accennano che Barriere 1 & 4 impediscono questa esempio da scrittura 0 e Barriere 2 & 3 fornire una garanzia freschezza: assicurano che se B correva dopo A, leggendo _completo valuterebbe a vero.

Non lo capisco davvero. Credo di capire il motivo per cui le barriere 1 & 4 sono necessari: non vogliamo la scrittura a _answer di ottimizzare e collocati dopo la scrittura per _complete (Barriera 1) e dobbiamo fare in modo che _answer è non memorizzato nella cache (barriera 4). Penso anche di capire perché è necessario Barrier 3: se A funzionasse solo dopo aver scritto _complete = true, B avrebbe comunque bisogno di aggiornare _completo per leggere il valore corretto.

Non capisco però perché abbiamo bisogno di Barriera 2! Una parte di me dice che forse il thread 2 (in esecuzione B) è già stato eseguito (ma non incluso) se (_completato) e quindi è necessario assicurare che _completo sia aggiornato.

Tuttavia, non vedo come questo aiuti. Non è ancora possibile che _completo sia impostato su true in A, ma il metodo B vedrà una versione cache (falsa) di _completo? Per esempio, se filettatura 2 riceve un metodo B fino a dopo la prima MemoryBarrier e poi filetto 1 corse metodo A fino _complete = true ma non oltre, e poi filetto 1 riprende e testato se (_complete) - potrebbe che se non risulta in false?

+0

Perché qualcuno dovrebbe usarlo su 'volatile'? – ChaosPandion

+6

@Chaos: CLR tramite C# book (Richter) ha una grande spiegazione - IIRC è che 'volatile' significa che tutti gli accessi al var sono trattati come volatili e impongono barriere di memoria complete in entrambe le direzioni. Questo è spesso il modo più perfetto del necessario se invece hai solo bisogno di una barriera di lettura o di scrittura e solo in particolari accessi. –

+1

@Chaos: non proprio il punto, ma una ragione è che volatile ha le sue peculiarità riguardo alle ottimizzazioni del compilatore che potrebbero portare a deadlock, vedere http://www.bluebytesoftware.com/blog/2009/02/24/TheMagicalDuelingDeadlockingSpinLocks .aspx – hackerhasid

risposta

24

La barriera n. 2 garantisce che la scrittura su _complete venga eseguita immediatamente. In caso contrario, potrebbe rimanere in uno stato in coda, ovvero la lettura di in B non vedrebbe la modifica causata da A anche se B ha effettivamente utilizzato una lettura volatile.

Ovviamente, questo esempio non rende giustizia al problema poiché A non esegue più nulla dopo la scrittura su _complete, il che significa che la scrittura verrà comunicata immediatamente comunque poiché il thread termina in anticipo.

La risposta alla tua domanda se lo if potrebbe ancora valutare su false è sì esattamente per le ragioni che hai dichiarato. Ma, nota ciò che l'autore dice riguardo a questo punto.

Le barriere 1 e 4 impediscono a questo esempio di 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 per vero.

L'enfasi su "se B corre dietro A" è mia. Certamente potrebbe essere il caso che i due fili si intrecciano. Ma l'autore stava ignorando questo scenario presumibilmente per esprimere il suo punto di vista su come il Thread.MemoryBarrier funzioni più facilmente.

A proposito, ho avuto difficoltà a creare un esempio sulla mia macchina in cui le barriere # 1 e # 2 avrebbero alterato il comportamento del programma. Questo perché il modello di memoria relativo alle scritture era forte nel mio ambiente. Forse, se avessi una macchina multiprocessore, usassi Mono, o avessi qualche altra configurazione diversa avrei potuto dimostrarlo. Certo, è stato facile dimostrare che rimuovere le barriere # 3 e # 4 ha avuto un impatto.

+0

Grazie, è stato utile. Immagino di non essere così indifferente come pensavo. – hackerhasid

+0

Non capisco che entrambe le barriere 2 * e * 3 siano necessarie nel caso in cui B funzioni dopo A. Entrambe sono recinti pieni, quindi nessuno di loro farebbe da soli, no? –

+5

@ohadsc: le barriere della memoria influenzano solo il comportamento di un singolo thread. Considera che A e B potrebbero essere in esecuzione su diverse CPU. Se hai rimosso la barriera 2, la scrittura potrebbe non essere eseguita. Se hai rimosso la barriera 3, la lettura potrebbe non essere aggiornata. Le barriere in A non hanno alcun impatto sull'esecuzione di B e viceversa. –

0

L'esempio non è chiaro per due motivi:

  1. E 'troppo semplice per mostrare appieno ciò che sta accadendo con le recinzioni.
  2. Albahari include i requisiti per architetture non-x86. Vedere MSDN: "MemoryBarrier è richiesto solo su sistemi multiprocessore con ordini di memoria deboli (ad esempio, un sistema che utilizza più processori Intel Itanium [che Microsoft non supporta più]).".

Se si considera quanto segue, diventa più chiaro:

  1. Una barriera di memoria (barriere completo qui - Net non fornisce un mezzo di barriera) impedisce leggere le istruzioni/scrittura di saltare la recinzione (a causa di varie ottimizzazioni). Questo ci garantisce il codice dopo che il recinto verrà eseguito dopo il codice prima della recinzione.
  2. "Questa operazione di serializzazione garantisce che tutte le istruzioni di caricamento e memorizzazione che precedono nell'ordine di programma l'istruzione MFENCE siano globalmente visibili prima che qualsiasi istruzione di caricamento o di memorizzazione che segue l'istruzione di MFENCE sia globalmente visibile." Vedi here.
  3. x86 Le CPU hanno un modello di memoria forte e garantiscono che le scritture siano coerenti con tutti i thread/core (quindi le barriere # 2 & # 3 non sono necessarie su x86). Ma non ci è garantito che le letture e le scritture rimangano nella sequenza codificata, da qui la necessità delle barriere # 1 e # 4.
  4. Le barriere di memoria sono inefficienti e non devono essere utilizzate (vedere lo stesso articolo MSDN). Io personalmente uso Interlocked e volatile (assicurati di sapere come usarlo correttamente !!), che funzionano in modo efficiente e sono facili da capire.

Ps. This article spiega bene il funzionamento interno di x86.

Problemi correlati