Come leggere correttamente un campo int Interlocked.Increment’ed?

Supponiamo che io abbia un campo int non volatile e un thread che è Interlocked.Increment . Un altro thread può leggerlo direttamente in modo sicuro o la lettura deve anche essere interbloccata?

In precedenza ho pensato che dovevo usare una lettura interbloccata per garantire che io vedessi il valore corrente, dato che, dopo tutto, il campo non è volatile. Sto usando Interlocked.CompareExchange(int, 0, 0) per raggiungere questo objective.

Tuttavia, mi sono imbattuto in questa risposta che suggerisce che in realtà le letture vedranno sempre la versione corrente di un valore Interlocked.Increment ed, poiché la lettura int è già atomica, non c’è bisogno di fare qualcosa di speciale. Ho anche trovato una richiesta in cui Microsoft rifiuta una richiesta di Interlocked.Read (ref int) , suggerendo inoltre che questo è completamente ridondante.

Quindi posso davvero tranquillamente leggere il valore più attuale di un tale campo int senza Interlocked ?

Se si desidera garantire che l’altro thread leggerà l’ultimo valore, è necessario utilizzare Thread.VolatileRead() . (*)

L’operazione di lettura stessa è atomica, quindi non causerà alcun problema, ma senza la lettura volatile si potrebbe ottenere un valore vecchio dalla cache o il compilatore potrebbe ottimizzare il codice ed eliminare del tutto l’operazione di lettura. Dal punto di vista del compilatore è sufficiente che il codice funzioni in ambiente a thread singolo. Le operazioni volatili e le barriere della memoria sono utilizzate per limitare la capacità del compilatore di ottimizzare e riordinare il codice.

Ci sono diversi partecipanti che possono modificare il codice: compilatore, compilatore JIT e CPU. Non importa quale di questi mostri che il tuo codice è rotto. L’unica cosa importante è il modello di memoria .NET in quanto specifica le regole che devono essere rispettate da tutti i partecipanti.

(*) Thread.VolatileRead() realtà non ottiene l’ultimo valore. Leggerà il valore e aggiungerà una barriera di memoria dopo la lettura. La prima lettura volatile potrebbe ottenere il valore memorizzato nella cache ma il secondo otterrebbe un valore aggiornato perché la barriera di memoria della prima lettura volatile ha forzato un aggiornamento della cache se fosse necessario. In pratica questo dettaglio ha poca importanza quando si scrive il codice.

Un po ‘di meta-problema, ma un buon aspetto dell’uso di Interlocked.CompareExchange(ref value, 0, 0) (ignorando l’ovvio svantaggio che è più difficile da capire quando usato per la lettura) è che funziona indipendentemente da int o long . È vero che le letture int sono sempre atomiche, ma long letture long non sono o potrebbero non esserlo, a seconda dell’architettura. Sfortunatamente, Interlocked.Read(ref value) funziona solo se il value è di tipo long .

Si consideri il caso in cui si inizia con un campo int , che rende imansible l’uso di Interlocked.Read() , quindi si leggerà direttamente il valore poiché esso è comunque atomico. Tuttavia, più avanti nello sviluppo tu o qualcun altro decidi che è richiesto un long : il compilatore non ti avviserà, ma ora potresti avere un piccolo bug: l’accesso in lettura non è più garantito per essere atomico. Ho trovato utilizzando Interlocked.CompareExchange() l’alternativa migliore qui; Potrebbe essere più lento a seconda delle istruzioni del processore sottostanti, ma è più sicuro a lungo termine. Non conosco abbastanza i componenti interni di Thread.VolatileRead() ; Potrebbe essere “migliore” per quanto riguarda questo caso d’uso poiché fornisce ancora più firme.

Non proverei a leggere direttamente il valore (cioè senza nessuno dei meccanismi sopra descritti) all’interno di un ciclo o qualsiasi chiamata di metodo stretta, poiché anche se le scritture sono volatili e / o memorizzate nella memoria, niente sta dicendo al compilatore che il il valore del campo può effettivamente cambiare tra due letture . Quindi, il campo dovrebbe essere volatile o qualsiasi dei costrutti dati dovrebbe essere usato.

I miei due centesimi.

Hai ragione a dire che non hai bisogno di un’istruzione speciale per leggere atomicamente un intero a 32 bit, tuttavia, ciò significa che otterrai il valore “intero” (ovvero non otterrai parte di una scrittura e parte di un’altra ). Non hai garanzie che il valore non sarà cambiato dopo averlo letto.

È a questo punto che devi decidere se è necessario utilizzare un altro metodo di sincronizzazione per controllare l’accesso, ad esempio se stai utilizzando questo valore per leggere un membro da un array, ecc.


In poche parole, l’ atomicità assicura che un’operazione avvenga in modo completo e indivisibile. Data una certa operazione A che conteneva N passi, se si è passati all’operazione subito dopo A si può essere certi che tutti i passaggi N si sono verificati in isolamento da operazioni concorrenti.

Se avessi due thread che eseguivano l’operazione atomica A sei sicuro che vedrai solo il risultato completo di uno dei due thread. Se si desidera coordinare i thread, è ansible utilizzare le operazioni atomiche per creare la sincronizzazione richiesta. Ma le operazioni atomiche di per sé non forniscono una sincronizzazione di livello superiore. La famiglia di metodi Interlocked è messa a disposizione per fornire alcune fondamentali operazioni atomiche.

La sincronizzazione è un tipo più ampio di controllo della concorrenza, spesso costruito attorno alle operazioni atomiche . La maggior parte dei processori include barriere di memoria che ti permettono di assicurare che tutte le linee della cache siano scaricate e che tu abbia una visione coerente della memoria. Le letture volatili sono un modo per garantire un accesso coerente a una determinata posizione di memoria.

Sebbene non sia immediatamente applicabile al tuo problema, la lettura di ACID (atomicità, coerenza, isolamento e durata) rispetto ai database può aiutarti con la terminologia.

Sì, tutto ciò che hai letto è corretto. Interlocked.Increment è progettato in modo che le letture normali non siano false durante le modifiche al campo. Leggere un campo non è pericoloso, scrivere un campo è.