l’assegnazione di riferimento è atomica, quindi perché è necessario Interlocked.Exchange (ref Object, Object)?

Nel mio servizio web asmx multithreaded avevo un campo di class _allData del mio tipo SystemData che consisteva in pochi List e Dictionary contrassegnati come volatile . I dati di sistema ( _allData ) vengono aggiornati una volta ogni tanto e lo faccio creando un altro object chiamato newData e riempiendo le sue strutture di dati con nuovi dati. Quando è finito, lo assegno

 private static volatile SystemData _allData public static bool LoadAllSystemData() { SystemData newData = new SystemData(); /* fill newData with up-to-date data*/ ... _allData = newData. } 

Questo dovrebbe funzionare poiché l’assegnazione è atomica e i thread che hanno il riferimento ai vecchi dati continuano a usarlo e gli altri hanno i nuovi dati di sistema subito dopo l’assegnazione. Tuttavia il mio collega ha detto che invece di usare parole chiave volatile e semplici InterLocked.Exchange dovrei usare InterLocked.Exchange perché ha affermato che su alcune piattaforms non è garantito che l’assegnazione di riferimento sia atomica. Inoltre: quando dichiaro the _allData campo the _allData come volatile

 Interlocked.Exchange(ref _allData, newData); 

produce avviso “un riferimento a un campo volatile non sarà trattato come volatile” Cosa dovrei pensare a questo?

Ci sono numerose domande qui. Considerandoli uno alla volta:

l’assegnazione di riferimento è atomica, quindi perché è necessario Interlocked.Exchange (ref Object, Object)?

L’assegnazione di riferimento è atomica. Interlocked.Exchange non esegue solo assegnazioni di riferimento. Fa una lettura del valore corrente di una variabile, ripara il vecchio valore e assegna il nuovo valore alla variabile, tutto come un’operazione atomica.

il mio collega ha detto che su alcune piattaforms non è garantito che l’assegnazione di riferimento sia atomica. La mia collega era corretta?

No. L’assegnazione di riferimento è garantita per essere atomica su tutte le piattaforms .NET.

Il mio collega sta ragionando da false premesse. Ciò significa che le loro conclusioni non sono corrette?

Non necessariamente. Il tuo collega potrebbe darti un buon consiglio per cattive ragioni. Forse c’è qualche altra ragione per cui dovresti usare Interlocked.Exchange. La programmazione senza blocco è faticosamente difficile e nel momento in cui ti allontani da pratiche consolidate sposate da esperti del settore, sei fuori dalle erbacce e rischi il peggior tipo di condizioni di gara. Non sono né un esperto in questo campo né un esperto del tuo codice, quindi non posso esprimere un giudizio in un modo o nell’altro.

produce avviso “un riferimento a un campo volatile non sarà trattato come volatile” Cosa dovrei pensare a questo?

Dovresti capire perché questo è un problema in generale. Ciò porterà a capire perché l’avvertimento non è importante in questo caso particolare.

Il motivo per cui il compilatore fornisce questo avvertimento è perché contrassegnare un campo come volatile significa “questo campo verrà aggiornato su più thread – non generare alcun codice che memorizza nella cache i valori di questo campo, e assicurarsi che eventuali letture o scritture di questo campo non viene “spostato avanti e indietro nel tempo” tramite incoerenze nella cache del processore. ”

(Suppongo che tu capisca tutto questo già.Se non hai una conoscenza dettagliata del significato di volatile e di come influisce sulla semantica della cache del processore allora non capisci come funziona e non dovresti usare volatile. sono molto difficili da ottenere, assicurati che il tuo programma sia giusto perché capisci come funziona, non per errore.)

Supponiamo ora di creare una variabile che è un alias di un campo volatile passando un riferimento a quel campo. All’interno del metodo chiamato, il compilatore non ha alcuna ragione per sapere che il riferimento deve avere una semantica volatile! Il compilatore genererà allegramente il codice per il metodo che non riesce a implementare le regole per i campi volatili, ma la variabile è un campo volatile. Ciò può distruggere completamente la logica lock-free; l’assunto è sempre che un campo volatile è sempre accessibile con la semantica volatile. Non ha senso trattarlo come volatile a volte e non altre volte; devi essere sempre coerente altrimenti non puoi garantire la coerenza su altri accessi.

Pertanto, il compilatore avverte quando lo fai, perché probabilmente sta andando a rovinare completamente la tua logica lock-free attentamente sviluppata.

Certo, Interlocked.Exchange è scritto per aspettarsi un campo volatile e fare la cosa giusta. L’avvertimento è quindi fuorviante. Mi dispiace molto; ciò che dovremmo fare è implementare un meccanismo in base al quale un autore di un metodo come Interlocked.Exchange potrebbe inserire un attributo nel metodo che dice “questo metodo che prende un riferimento per forzare la semantica volatile sulla variabile, quindi sopprimiamo l’avviso”. Forse in una versione futura del compilatore lo faremo.

O il tuo collega si sbaglia, o sa qualcosa che le specifiche del linguaggio C # non lo fanno.

5.5 Atomicità dei riferimenti variabili :

“Le letture e le scritture dei seguenti tipi di dati sono atomiche: bool, char, byte, sbyte, short, ushort, uint, int, float e tipi di riferimento.”

Pertanto, è ansible scrivere sul riferimento volatile senza il rischio di ottenere un valore danneggiato.

Ovviamente dovresti fare attenzione a come decidi quale thread deve recuperare i nuovi dati, per minimizzare il rischio che più di un thread alla volta lo faccia.

Interlocked.Exchange

Imposta una variabile del tipo specificato T su un valore specificato e restituisce il valore originale, come operazione atomica.

Cambia e restituisce il valore originale, è inutile perché vuoi solo cambiarlo e, come ha detto Guffa, è già atomico.

A meno che un profiler non abbia dimostrato che si tratta di un collo di bottiglia nella tua applicazione, dovresti prendere in considerazione la possibilità di bloccare i blocchi, è più facile da capire e dimostrare che il tuo codice è corretto.

Iterlocked.Exchange() non è solo atomico, ma si occupa anche della visibilità della memoria:

Le seguenti funzioni di sincronizzazione utilizzano le barriere appropriate per garantire l’ordine di memoria:

Funzioni che entrano o escono da sezioni critiche

Funzioni che segnalano oggetti di sincronizzazione

Funzioni di attesa

Funzioni interbloccate

Problemi di sincronizzazione e multiprocessore

Ciò significa che oltre all’atomicità garantisce che:

  • Per il thread chiamandolo:
    • Nessun riordino delle istruzioni è fatto (dal compilatore, dal tempo di esecuzione o dall’hardware).
  • Per tutti i thread:
    • Non ci sono letture alla memoria che avvengono prima che questa istruzione vedrà il cambiamento apportato da questa istruzione.
    • Tutte le letture dopo questa istruzione vedranno la modifica apportata da questa istruzione.
    • Tutto scrive in memoria dopo che questa istruzione si è verificata dopo che questa modifica delle istruzioni ha raggiunto la memoria principale (svuotando questa modifica delle istruzioni nella memoria principale quando è stata eseguita e non lasciando che l’hardware fluisca sul tempo impostato).