c# - significa - ¿Para qué se usa la palabra clave "volátil"?
volatile c# (7)
Considera este ejemplo:
int i = 5;
System.out.println(i);
El compilador puede optimizar esto para imprimir solo 5, así:
System.out.println(5);
Sin embargo, si hay otro hilo que puede cambiar i
, este es el comportamiento incorrecto. Si otro hilo cambia i
para que sea 6, la versión optimizada seguirá imprimiendo 5.
La palabra clave volatile
impide dicha optimización y el almacenamiento en caché, y por lo tanto es útil cuando una variable puede ser cambiada por otro hilo.
Leí algunos artículos sobre la palabra clave volatile
pero no pude entender su uso correcto. ¿Podrías decirme por qué debería usarse en C # y en Java?
Cuando está leyendo datos que no son volátiles, el hilo de ejecución puede o no obtener siempre el valor actualizado. Pero si el objeto es volátil, el hilo siempre obtiene el valor más actualizado.
En Java, "volátil" se usa para indicarle a la JVM que la variable puede ser utilizada por múltiples hilos al mismo tiempo, por lo que no se pueden aplicar ciertas optimizaciones comunes.
Cabe destacar la situación en la que los dos subprocesos que acceden a la misma variable se ejecutan en CPU separadas en la misma máquina. Es muy común que las CPU guarden en caché de forma agresiva los datos que contiene porque el acceso a la memoria es mucho más lento que el acceso a la memoria caché. Esto significa que si los datos se actualizan en la CPU1 deben pasar inmediatamente por todas las memorias caché y la memoria principal en lugar de cuando la memoria caché decide borrarse, de modo que la CPU2 pueda ver el valor actualizado (nuevamente ignorando todas las memorias caché en el camino).
La palabra clave volátil tiene diferentes significados tanto en Java como en C #.
Java
De la especificación de lenguaje de Java :
Un campo puede declararse volátil, en cuyo caso el modelo de memoria de Java asegura que todos los subprocesos vean un valor constante para la variable.
DO#
De la referencia C # en la palabra clave volátil :
La palabra clave volátil indica que un campo puede ser modificado en el programa por algo como el sistema operativo, el hardware o un hilo de ejecución simultáneo.
Las lecturas de campos volátiles han adquirido semántica . Esto significa que se garantiza que la lectura de memoria de la variable volátil se producirá antes de que se lea la siguiente memoria. Bloquea al compilador para que no realice el reordenamiento, y si el hardware lo requiere (CPU ordenada débilmente), usará una instrucción especial para hacer que el hardware limpie las lecturas que ocurran después de la lectura volátil pero que se iniciaron especulativamente antes, o la CPU podría evitar que se emitan desde el principio, al evitar que se produzca una carga especulativa entre la emisión de la carga adquirida y su retiro.
Las escrituras de campos volátiles tienen semántica de lanzamiento . Esto significa que se garantiza que cualquier escritura en la variable volátil se demorará hasta que todas las escrituras de memoria anteriores sean visibles para otros procesadores.
Considere el siguiente ejemplo:
something.foo = new Thing();
Si foo
es una variable miembro en una clase, y otras CPU tienen acceso a la instancia del objeto al que hace referencia something
, ¡pueden ver que el valor foo
cambiar antes de que las escrituras de la memoria en el constructor Thing
sean globalmente visibles! Esto es lo que significa "memoria débilmente ordenada". Esto podría ocurrir incluso si el compilador tiene todas las tiendas en el constructor antes de la tienda para foo
. Si foo
es volatile
, el store to foo
tendrá una semántica de lanzamiento, y el hardware garantiza que todas las escrituras antes de la escritura son visibles para otros procesadores antes de permitir que ocurra la escritura.
¿Cómo es posible que las escrituras foo
sean reordenadas tan mal? Si la línea de caché que contiene foo
está en la memoria caché, y las tiendas en el constructor perdieron la memoria caché, entonces es posible que la tienda se complete mucho antes de lo que faltan las escrituras en la memoria caché.
La (horrible) arquitectura Itanium de Intel tenía una memoria débilmente ordenada. El procesador utilizado en el XBox 360 original tenía una memoria débilmente ordenada. Muchos procesadores ARM, incluido el muy popular ARMv7-A tienen memoria ordenada débilmente.
Los desarrolladores a menudo no ven estas carreras de datos porque cosas como bloqueos harán una barrera de memoria completa, esencialmente lo mismo que adquirir y liberar semántica al mismo tiempo. No se pueden ejecutar especulativamente las cargas dentro de la cerradura antes de que se adquiera la cerradura, sino que se retrasan hasta que se adquiere la cerradura. No se pueden retrasar las tiendas a través de un lanzamiento de bloqueo, la instrucción que libera el bloqueo se retrasa hasta que todas las escrituras hechas dentro del bloqueo estén visibles globalmente.
Un ejemplo más completo es el patrón "Bloqueo comprobado doble". El propósito de este patrón es evitar tener que adquirir siempre un bloqueo para inicializar un objeto de forma lenta.
Enganchado de Wikipedia:
public class MySingleton {
private static object myLock = new object();
private static volatile MySingleton mySingleton = null;
private MySingleton() {
}
public static MySingleton GetInstance() {
if (mySingleton == null) { // 1st check
lock (myLock) {
if (mySingleton == null) { // 2nd (double) check
mySingleton = new MySingleton();
// Write-release semantics are implicitly handled by marking mySingleton with
// ''volatile'', which inserts the necessary memory barriers between the constructor call
// and the write to mySingleton. The barriers created by the lock are not sufficient
// because the object is made visible before the lock is released.
}
}
}
// The barriers created by the lock are not sufficient because not all threads will
// acquire the lock. A fence for read-acquire semantics is needed between the test of mySingleton
// (above) and the use of its contents.This fence is automatically inserted because mySingleton is
// marked as ''volatile''.
return mySingleton;
}
}
En este ejemplo, las tiendas en el constructor MySingleton
pueden no ser visibles para otros procesadores antes de la tienda en mySingleton
. Si eso sucede, los otros subprocesos que miran en mySingleton no adquirirán un bloqueo y no recogerán necesariamente las escrituras en el constructor.
volatile
nunca previene el almacenamiento en caché. Lo que hace es garantizar el orden en que otros procesadores "ven" escribe. Una versión de la tienda retrasará una tienda hasta que se completen todas las escrituras pendientes y se haya emitido un ciclo de bus indicando a otros procesadores que descarten / escriban de nuevo su línea de caché si tienen las líneas relevantes en caché. Una carga adquirida eliminará las lecturas especificadas, asegurándose de que no sean valores obsoletos del pasado.
Para comprender lo que hace volátil a una variable, es importante entender qué sucede cuando la variable no es volátil.
- La variable no es volátil
Cuando dos hilos A y B acceden a una variable no volátil, cada hilo mantendrá una copia local de la variable en su caché local. Cualquier cambio realizado por el subproceso A en su caché local no será visible para el subproceso B.
- La variable es volátil
Cuando las variables se declaran volátiles, esencialmente significa que los subprocesos no deben almacenar en caché dicha variable o, en otras palabras, los subprocesos no deben confiar en los valores de estas variables a menos que se lean directamente desde la memoria principal.
Entonces, ¿cuándo hacer que una variable sea volátil?
Cuando tiene una variable a la que se puede acceder mediante muchos subprocesos y quiere que cada subproceso obtenga el último valor actualizado de esa variable, incluso si el valor se actualiza por cualquier otro subproceso / proceso / fuera del programa.
Tanto para C # como para Java, "volátil" le dice al compilador que el valor de una variable nunca debe almacenarse en caché ya que su valor puede cambiar fuera del alcance del programa. El compilador evitará cualquier optimización que pueda ocasionar problemas si la variable cambia "fuera de su control".