2009-02-05 17 views
9

Sono confuso. Le risposte al mio previous question sembrano confermare le mie supposizioni. Ma come dichiarato here volatile non è sufficiente per assicurare l'atomicità in. Netto. Entrambe le operazioni come l'incremento e l'assegnazione in MSIL non vengono tradotte direttamente in OPCODE nativo, o molte CPU possono leggere e scrivere contemporaneamente nella stessa posizione RAM.Perché volatile non è abbastanza?

per chiarire:

  1. Voglio sapere se scrive e legge sono atomica su più CPU?
  2. Capisco cos'è volatile. Ma è abbastanza? Devo utilizzare le operazioni interbloccate se voglio ottenere l'ultimo valore scritto da un'altra CPU?

risposta

10

Herb Sutter ha recentemente scritto un articolo su volatile e cosa significa realmente (come influisce sull'ordinamento dell'accesso alla memoria e dell'atomicità) nel C++ nativo. .NET e ambienti Java. E 'una buona lettura:

+3

È * una lettura abbastanza buona (e sicuramente merita un +1), ma di solito è preferibile che le risposte SO siano alquanto autonome. Sarebbe bello se la tua risposta contenesse un riassunto delle parti importanti di quell'articolo, solo così (1) le persone che sfiorano la pagina lo vedranno e (2) in modo che la risposta rimanga pertinente se l'articolo viene spostato o rimosso. – jalf

-1

Volatile è una parola chiave del compilatore che indica al compilatore cosa fare. Non si traduce necessariamente in operazioni (essenzialmente) di bus richieste per l'atomicità. Di solito è lasciato al sistema operativo.

Modifica: per chiarire, volatile non è mai abbastanza se si vuole garantire l'atomicità. O meglio, spetta al compilatore renderlo abbastanza o meno.

+0

duh. ma per quanto riguarda l'incremento e l'assegnazione? –

+0

Se si parla di operazioni interbloccate, ovvero operazioni atomiche garantite, è necessario implementare queste due operazioni tramite le operazioni bus (x86 utilizza il modificatore opcode del blocco) o il sistema operativo (PPC deve utilizzare lwarx/stwrx per eseguire l'equivalente). – MSN

+0

Perché è necessaria un'operazione di bus per l'atomicità, specialmente se la CPU è una di Core2Duo? E lasciato al sistema operativo? Stai scherzando! –

6

volatile in .NET rende l'accesso alla variabile atomica.

Il problema è che spesso non è sufficiente. Cosa succede se hai bisogno di leggere la variabile, e se è 0 (indicando che la risorsa è gratuita), la imposti su 1 (che indica che è bloccata, e altri thread dovrebbero starne lontano).

La lettura dello 0 è atomico. Scrivere l'1 è atomico. Ma tra queste due operazioni, qualsiasi cosa potrebbe accadere. Si potrebbe leggere un 0, e quindi prima di poter scrivere la 1, un altro thread salta, si legge nella 0, e scrive un 1.

Tuttavia, volatile in .NET fa garanzia atomicità degli accessi alla variabile. Semplicemente non garantisce la sicurezza del thread per le operazioni che si basano su accessi multipli ad esso. (Disclaimer: volatile in C/C++ non garantisce nemmeno questo. Solo così lo sai.E 'molto più debole, e occasionalmente una fonte di bug perché la gente si assume garantisce atomicità :))

Quindi è necessario utilizzare le serrature come bene, per raggruppare più operazioni come un unico blocco thread-safe. (Oppure, per operazioni semplici, le operazioni di Interlocked in .NET possono fare il trucco)

+0

Nota che non è volatile che garantisce l'atomicità alle variabili - è il contrario: se si può accedere a una variabile atomicamente, è possibile applicare "volatile" ad esso. – xxbbcc

0

Il problema si presenta con le copie incassate basate sui registri dei valori della variabile.

Durante la lettura di un valore, la CPU prima vedrà se si trova in un registro (veloce) prima di controllare la memoria principale (più lenta).

Volatile indica al compilatore di inviare il valore alla memoria principale al più presto e di non fidarsi del valore del registro memorizzato. È utile solo in alcuni casi.

Se stai cercando scritture di codice op singolo, dovrai utilizzare i metodi Interlocked.Increment correlati. Ma sono piuttosto limitati in quello che possono fare in una singola istruzione sicura.

La scommessa più sicura e più affidabile è bloccare() (se non è possibile eseguire un interblocco.*)

Modifica: le scritture e le letture sono atomiche se sono in un blocco o in una istruzione interbloccata. *. Volatile da solo non è sufficiente sotto i termini della tua domanda

5

Qui potrei saltare la pistola ma mi sembra che tu stia confondendo due problemi qui.

Uno è l'atomicità, che nella mia mente significa che una singola operazione (che potrebbe richiedere più passaggi) non dovrebbe entrare in conflitto con un'altra operazione di questo tipo.

L'altro è la volatilità, quando è previsto che questo valore cambi e perché.

Prendere il primo. Se la tua operazione in due passaggi richiede di leggere il valore corrente, modificarlo e scriverlo di nuovo, sicuramente vorrai un blocco, a meno che l'intera operazione non possa essere tradotta in una singola istruzione della CPU che possa funzionare su un singola riga di dati della cache.

Tuttavia, il secondo problema è che, anche quando si sta eseguendo il blocco, cosa vedranno gli altri thread.

Un campo volatile in .NET è un campo che il compilatore sa può cambiare in momenti arbitrari. In un mondo a thread singolo, il cambiamento di una variabile è qualcosa che accade ad un certo punto in un flusso sequenziale di istruzioni in modo che il compilatore sappia quando ha aggiunto il codice che lo modifica, o almeno quando ha chiamato al mondo esterno che può o non può averlo cambiato in modo tale che una volta che il codice ritorna, potrebbe non essere lo stesso valore che era prima della chiamata.

Questa conoscenza consente al compilatore di sollevare il valore dal campo in un registro una volta, prima di un ciclo o un simile blocco di codice e di non rileggere mai il valore dal campo per quel particolare codice.

Con multi-threading, tuttavia, ciò potrebbe causare alcuni problemi. Un thread potrebbe aver regolato il valore e un altro thread, a causa dell'ottimizzazione, non leggerà questo valore per un po 'di tempo, perché lo conosce non è stato modificato.

Quindi quando contrassegni un campo come volatile stai dicendo al compilatore che non dovrebbe presumere che abbia il valore corrente di questo in qualsiasi momento, eccetto per l'acquisizione di istantanee ogni volta che ha bisogno del valore.

I blocchi risolvono operazioni a più fasi, la volatilità gestisce il modo in cui il compilatore memorizza il valore del campo in un registro e insieme risolveranno più problemi.

Si noti inoltre che se un campo contiene qualcosa che non può essere letto in una singola istruzione cpu, molto probabilmente si vorrà bloccare anche l'accesso in lettura ad esso.

Ad esempio, se si utilizza una CPU a 32 bit e si scrive un valore a 64 bit, l'operazione di scrittura richiederà due passaggi e se un altro thread su un'altra CPU riesce a leggere il 64-bit il valore prima che il passaggio 2 sia completato, otterrà metà del valore precedente e metà del nuovo, ben mescolato insieme, il che può essere anche peggiore rispetto a ottenere uno obsoleto.


Modifica: Per rispondere il commento, che volatile garantisce l'atomicità delle operazioni di lettura/scrittura, che è bene, vero, in un certo senso, perché la parola chiave volatile non può essere applicato ai campi di dimensioni superiori 32-bit, in effetti rendendo il campo single-cpu-instruction leggibile/scrivibile su cpu sia a 32 che a 64 bit.E sì, impedirà che il valore venga tenuto in un registro il più possibile.

Quindi parte del commento è errata, volatile non può essere applicato a valori a 64 bit.

Si noti inoltre che volatile ha alcune semantiche relative al riordino delle letture/scritture.

Per informazioni importanti, vedere MSDN documentation o C# specification, trovato here, sezione 10.5.3.

+0

Giusto per essere chiari, la tua descrizione di suoni volatili è più simile a quella che si trova in C/C++. In .net, volatile garantisce l'atomicità e lo costringe a rimanere in memoria, piuttosto che un registro. Anche su valori più grandi, come 64-bit. – jalf

+0

@jalf: come si specifica volatile su valori a 64 bit in C# ??? –

1

A livello hardware, più CPU può mai scrivere simultanously nella stessa posizione RAM atomica. Le dimensioni di un'operazione di lettura/scrittura atomica dipendono dall'architettura della CPU, ma in genere sono 1, 2 o 4 byte su un'architettura a 32 bit. Tuttavia, se provi a leggere il risultato, c'è sempre la possibilità che un'altra CPU abbia fatto una scrittura nella stessa posizione RAM tra di loro. A un livello basso, i blocchi di rotazione sono in genere utilizzati per sincronizzare l'accesso alla memoria condivisa. In un linguaggio di alto livello, tali meccanismi possono essere chiamati per es. regioni critiche.

Il tipo volatile si limita ad assicurare che la variabile venga immediatamente riportata in memoria quando viene modificata (anche se il valore deve essere utilizzato nella stessa funzione). Un compilatore di solito mantiene un valore in un registro interno il più a lungo possibile se il valore deve essere riutilizzato più tardi nella stessa funzione, e viene memorizzato di nuovo nella RAM quando tutte le modifiche sono terminate o quando una funzione ritorna. I tipi volatili sono per lo più utili quando si scrive su registri hardware o quando si vuole essere certi che un valore sia memorizzato nella RAM, ad es. un sistema multithread.

Problemi correlati