È un uso corretto di Thread.MemoryBarrier ()?

Supponiamo che abbia un campo che controlla l’esecuzione di un ciclo:

private static bool shouldRun = true; 

E ho un thread in esecuzione, che ha il codice come:

 while(shouldRun) { // Do some work .... Thread.MemoryBarrier(); } 

Ora, un altro thread potrebbe impostare shouldRun su false , senza utilizzare alcun meccanismo di sincronizzazione.

Per quanto comprendo Thread.MemoryBarrier (), avere questa chiamata all’interno del ciclo while impedirà al mio thread di lavoro di ottenere una versione memorizzata nella cache di shouldRun , evitando efficacemente un loop infinito.

La mia comprensione di Thread.MemoryBarrier è corretta? Dato che ho thread che possono impostare la variabile shouldRun (questo non può essere facilmente modificato), questo è un modo ragionevole per garantire che il mio ciclo si fermi una volta shouldRun è impostato su false da qualsiasi thread?

    È un uso corretto di Thread.MemoryBarrier ()?

    No. Supponiamo che un thread imposti il ​​flag prima che il ciclo inizi ad essere eseguito. Il ciclo potrebbe ancora essere eseguito una volta, utilizzando un valore memorizzato nella cache del flag. È corretto ? Sicuramente mi sembra sbagliato. Mi aspetterei che se imposto il flag prima della prima esecuzione del ciclo, che il ciclo venga eseguito zero volte, non una volta.

    Per quanto comprendo Thread.MemoryBarrier (), avere questa chiamata all’interno del ciclo while impedirà al mio thread di lavoro di ottenere una versione memorizzata nella cache di shouldRun, evitando efficacemente un loop infinito. La mia comprensione di Thread.MemoryBarrier è corretta?

    La barriera di memoria garantirà che il processore non esegua alcun riordino delle letture e delle scritture in modo tale che un accesso alla memoria che è logicamente prima della barriera venga effettivamente osservato dopo di esso, e viceversa.

    Se sei deciso a fare un codice di blocco basso, sarei incline a rendere il campo volatile piuttosto che introdurre un’esplicita barriera di memoria. “volatile” è una funzionalità del linguaggio C #. Una caratteristica pericolosa e poco conosciuta, ma una caratteristica della lingua. Comunica chiaramente al lettore del codice che il campo in questione verrà utilizzato senza blocchi su più thread.

    questo è un modo ragionevole per assicurare che il mio ciclo si fermi una volta dovrebbe Run è impostato su false da qualsiasi thread?

    Alcune persone lo considererebbero ragionevole. Non lo farei nel mio codice senza una ragione molto, molto buona.

    In genere, le tecniche di blocco basso sono giustificate da considerazioni sulle prestazioni. Ci sono due considerazioni del genere:

    Primo, un blocco conteso è potenzialmente estremamente lento; blocca finché c’è codice in esecuzione nella serratura. Se hai un problema di prestazioni perché c’è troppa contesa, allora proverei prima a risolvere il problema eliminando la contesa. Solo se non avessi potuto eliminare la contesa, avrei optato per una tecnica low-lock.

    In secondo luogo, potrebbe essere che un blocco non mirato sia troppo lento. Se il “lavoro” che si sta facendo nel ciclo prende, diciamo, meno di 200 nanosecondi, quindi il tempo necessario per controllare il blocco non mirato – circa 20 ns – è una frazione significativa del tempo dedicato al lavoro. In tal caso, suggerirei di fare più lavoro per ciclo . È davvero necessario che il loop si fermi entro 200 ns dal flag di controllo impostato?

    Solo negli scenari di prestazioni più estremi immagino che il costo di controllare un blocco non vincolato sia una frazione significativa del tempo trascorso nel programma.

    E anche, naturalmente, se stai inducendo una barriera di memoria ogni 200 ns o giù di lì, potresti anche distruggere le prestazioni in altri modi. Il processore vuole rendere per te l’ottimizzazione della memoria in movimento-in-around-in-time; se stai forzando ad abbandonare costantemente quelle ottimizzazioni, ti stai perdendo una potenziale vittoria.

    Credo che la tua comprensione sia un po ‘fuori da questa linea

    Per quanto comprendo Thread.MemoryBarrier (), avere questa chiamata all’interno del ciclo while impedirà al mio thread di lavoro di ottenere una versione memorizzata nella cache di shouldRun, evitando efficacemente un loop infinito.

    Una barriera di memoria è un modo per forzare un vincolo di ordinamento sulle istruzioni di lettura / scrittura. Mentre i risultati del riordino di lettura / scrittura possono avere l’aspetto di memorizzare nella cache una barriera di memoria in realtà non influisce in alcun modo sul caching. Agisce semplicemente come una barriera su cui le istruzioni di lettura e scrittura non possono attraversare.

    Con ogni probabilità questo non impedirà un ciclo infinito. Ciò che sta facendo il recinto di memoria è che questo scenario costringe tutte le letture e le scritture che si verificano nel corpo del ciclo a verificarsi prima che il valore di shouldRun venga letto dal ciclo condizionale

    Wikipedia ha una bella panoramica sulla barriera della memoria che potresti trovare utile

    A seconda di cosa stai provando a fare, probabilmente questo non farà quello che ti aspetti. Da MSDN , MemoryBarrier:

    Sincronizza l’accesso alla memoria come segue: Il processore che esegue il thread corrente non può riordinare le istruzioni in modo tale che l’accesso alla memoria prima della chiamata a MemoryBarrier venga eseguito dopo gli accessi alla memoria che seguono la chiamata a MemoryBarrier.

    Ciò impedirà il riordino delle istruzioni dopo la chiamata alla barriera di memoria prima di esso, ma ciò non impedirà alla programmazione dei thread di decidere che il ciclo debba avere un altro giro prima che il thread del writer esegua effettivamente la scrittura, ad esempio non è un meccanismo di blocco o sincronizzazione. Impedisce solo che altre istruzioni nel ciclo (come l’inizializzazione delle variabili) vengano riordinate prima del controllo del valore di shouldRun.

    Allo stesso modo, lasciare questo fuori non causerà un ciclo infinito, in entrambi i casi, dovrebbe essere eseguito con ogni iterazione. Non c’è “valore memorizzato nella cache” qui.

    Questa non è una risposta diretta alla domanda; tuttavia, questo sembra essere ben trattato nei post precedenti. Ciò che non sembra essere chiaramente affermato è come ottenere il risultato desiderato. Non c’è davvero niente di sbagliato nell’usare solo gli oggetti di sincronizzazione del thread previsti:

     private static readonly ManualResetEvent shutdownEvent = new ManualResetEvent(false); //Thread 1 - continue until event is signaled while(!shutodwnEvent.WaitOne(0)) { // Do some work .... Thread.MemoryBarrier(); } //Thread 2 - signal the other thread shutdownEvent.Set(); 

    L’altro approccio consiste nell’utilizzare un’istruzione di blocco quando si accede alla variabile:

     private static object syncRoot = new Object(); private static bool shouldRun = true; //Thread 1 bool bContinue; lock(syncRoot) bContinue = shouldRun; while(bContinue) { // Do some work .... lock(syncRoot) bContinue = shouldRun; } //Thread 2 lock(syncRoot) shouldRun = false; 

    In questo caso Thread.MemoryBarrier(); è una variante meno performante, ma non meno sicura di

     private static volatile readonly bool shouldRun = true; 

    perché la dichiarazione di shouldRun come volatile eseguirà una barriera di memoria se ciò è necessario per ottenere una lettura aggiornata, ma potrebbe non essere necessario farlo.