In che modo C # garantisce l’atomicità delle operazioni di lettura / scrittura?

Gli stati della specifica C # nella sezione 5.5 che legge e scrive su determinati tipi (vale a dire bool , char , byte , sbyte , short , ushort , uint , int , float e tipi di riferimento) sono garantiti come atomici.

Questo ha suscitato il mio interesse. Come puoi farlo? Voglio dire, la mia scarsa esperienza personale mi ha solo mostrato di bloccare variabili o di usare barriere se volevo leggere e scrivere per sembrare atomico; quello sarebbe un killer di prestazioni se dovesse essere fatto per ogni singola lettura / scrittura. Eppure C # fa qualcosa con un effetto simile.

Forse altri linguaggi (come Java) lo fanno. Io seriamente non lo so. La mia domanda non è pensata per essere specifica della lingua, è solo che so che C # lo fa.

Capisco che potrebbe avere a che fare con alcune specifiche istruzioni del processore e potrebbe non essere utilizzabile in C / C ++. Tuttavia, mi piacerebbe ancora sapere come funziona.

[EDIT] A dire il vero, credevo che le letture e le scritture potessero essere non atomiche in determinate condizioni, come una CPU potrebbe accedere a una locazione di memoria mentre un’altra CPU sta scrivendo lì. Succede solo quando la CPU non è in grado di trattare tutti gli oggetti contemporaneamente, ad esempio perché è troppo big o perché la memoria non è allineata sul limite corretto?

La ragione per cui tali tipi hanno garantito l’atomicità è perché sono tutti 32 bit o più piccoli. Poiché .NET funziona solo su sistemi operativi a 32 e 64 bit, l’architettura del processore può leggere e scrivere l’intero valore in un’unica operazione. Ciò è in contrasto con un Int64 su una piattaforma a 32 bit che deve essere letto e scritto utilizzando due operazioni a 32 bit.

Non sono un ragazzo dell’hardware, quindi mi scuso se la mia terminologia mi fa sembrare un buffone ma è l’idea di base.

È abbastanza economico implementare la garanzia di atomicità su core x86 e x64 poiché il CLR promette solo l’atomicità per variabili che sono a 32 bit o inferiori. Tutto ciò che è richiesto è che la variabile sia allineata correttamente e non si trovi a cavallo di una linea della cache. Il compilatore JIT lo garantisce allocando le variabili locali su un offset di stack allineato a 4 byte. Il gestore di heap GC fa lo stesso per le allocazioni di heap.

Notevole è che la garanzia CLR non è molto buona. La promise di allineamento non è sufficiente per scrivere codice coerente per gli array di doppi. Molto ben dimostrato in questo thread . Interoperare con il codice macchina che utilizza le istruzioni SIMD è anche molto difficile per questo motivo.

Su x86 le letture e le scritture sono comunque atomiche. È supportato a livello hardware. Questo tuttavia non significa che operazioni come addizione e moltiplicazione siano atomiche; richiedono un carico, calcolare e quindi archiviare, il che significa che possono interferire. È qui che entra in gioco il prefisso di blocco.

Hai menzionato le barriere di blocco e di memoria; non hanno nulla a che fare con le letture e scrive di essere atomico. Non c’è modo su x86 con o senza l’uso di barriere di memoria che vedrai un valore a 32 bit scritto a metà.

Sì, C # e Java garantiscono che i carichi e le memorie di alcuni tipi primitivi sono atomici, come dici tu. Questo è economico perché i processori in grado di eseguire .NET o JVM garantiscono che i carichi e le memorie di tipi primitivi adeguatamente allineati siano atomici.

Ora, ciò che né C # né Java, né i processori che gestiscono garantiscono, e che è costoso, sta emettendo barriere di memoria in modo che tali variabili possano essere utilizzate per la sincronizzazione in un programma multi-thread. Tuttavia, in Java e C # è ansible contrassegnare la variabile con l’attributo “volatile”, nel qual caso il compilatore si occupa di emettere le appropriate barriere di memoria.

Non puoi Anche andando fino in fondo al linguaggio assembly, devi usare opcode speciali di LOCK per garantire che un altro core o processo non si verifichi e cancelli tutto il tuo duro lavoro.