2009-02-05 15 views
9

Estoy confundido. Las respuestas a mi previous question parecen confirmar mis suposiciones. Pero como se indica here volátil no es suficiente para asegurar la atomicidad en .Net. O bien operaciones como el incremento y la asignación en MSIL no se traducen directamente a un solo OPCODE nativo o muchas CPU pueden leer y escribir simultáneamente en la misma ubicación de RAM.¿Por qué la volatilidad no es suficiente?

Para aclarar:

  1. Quiero saber si escribe y lee son atómica sobre varias CPU?
  2. Entiendo de qué se trata el volátil. ¿Pero es suficiente? ¿Debo usar operaciones interconectadas si deseo obtener el último valor escrito por otra CPU?

Respuesta

10

Herb Sutter escribió recientemente un artículo sobre volatile y lo que realmente significa (cómo afecta el ordenamiento del acceso a la memoria y la atomicidad) en el C++ nativo. .NET y entornos Java. Es una muy buena lectura:

+3

Es * una lectura bastante buena (y definitivamente merece un +1), pero normalmente se prefiere que las respuestas SO sean un tanto independientes. Sería bueno que su respuesta contuviera un resumen de las partes importantes de ese artículo, solo para que (1) las personas que roban la página lo vean y (2) para que la respuesta permanezca relevante si el artículo se mueve o se quita. – jalf

-1

Volatile es una palabra clave del compilador que le dice al compilador qué hacer. No se traduce necesariamente en (esencialmente) operaciones de bus que son necesarias para la atomicidad. Eso generalmente se deja al sistema operativo.

Editar: para aclarar, volátil nunca es suficiente si se quiere garantizar la atomicidad. O más bien, depende del compilador hacer que sea suficiente o no.

+0

duh. pero ¿qué pasa con el incremento y la asignación? –

+0

Si está hablando de operaciones interconectadas, es decir, operaciones atómicas garantizadas, esas dos deben implementarse ya sea mediante operaciones de bus (x86 usa el modificador de bloqueo de opcódigo) o el sistema operativo (PPC necesita usar lwarx/stwrx para hacer el equivalente). – MSN

+0

¿Por qué se necesitaría una operación de bus para la atomicidad, especialmente si su CPU es una de Core2Duo? Y dejó hasta el sistema operativo? ¡Tienes que estar bromeando! –

6

volátil en .NET hace el acceso a la variable atómica.

El problema es que a menudo eso no es suficiente. ¿Qué pasa si necesita leer la variable, y si es 0 (lo que indica que el recurso es gratuito), lo establece en 1 (lo que indica que está bloqueado, y otros hilos deben mantenerse alejados de él).

Leyendo el 0 es atómico. Escribir el 1 es atómico. Pero entre esas dos operaciones, cualquier cosa podría suceder. Es posible leer un 0, y luego antes de poder escribir el 1, otro hilo salta, se lee en el 0, y escribe un 1.

Sin embargo, volátil en .NET hace garantía de atomicidad de accesos a la variable. Simplemente no garantiza la seguridad de subprocesos para operaciones que dependen de múltiples accesos a la misma. (Descargo de responsabilidad: volátil en C/C++ ni siquiera garantiza esto. Para que lo sepas. Es mucho más débil, y de vez en cuando una fuente de errores porque la gente asume que garantiza la atomicidad :))

Así que debes usar bloqueos como bien, para agrupar varias operaciones como un pedazo seguro para subprocesos. (O, para operaciones simples, las operaciones Interlocked en .NET pueden hacer el truco)

+0

Tenga en cuenta que no es volátil que garantiza la atomicidad de las variables; es al revés: si se puede acceder a una variable atómicamente, se le puede aplicar 'volátil'. – xxbbcc

0

Su pregunta no tiene mucho sentido, porque volatile specifies the how the read happens, no la atomicidad de los procesos de varios pasos. Mi auto tampoco corta mi césped, pero trato de no mantenerlo en su lugar. :)

0

El problema viene con el registro basado en copias en efectivo de los valores de su variable.

Al leer un valor, la CPU primero verá si está en un registro (rápido) antes de verificar la memoria principal (más lento).

Volatile le dice al compilador que envíe el valor a la memoria principal lo antes posible, y que no confíe en el valor del registro en caché. Solo es útil en ciertos casos.

Si está buscando escrituras de código de operación única, necesitará usar los métodos relacionados con Interlocked.Increment. Pero están bastante limitados en lo que pueden hacer en una sola instrucción segura.

La apuesta más segura y confiable es bloquear() (si no puede hacer un enclavamiento).*)

Editar: Las escrituras y lecturas son atómicas si están en un candado o en una declaración * enclavada. Volátil por sí solo no es suficiente bajo los términos de su pregunta

5

Podría estar saltando la pistola aquí, pero me parece que está confundiendo dos cuestiones aquí.

Uno es la atomicidad, que en mi mente significa que una sola operación (que puede requerir varios pasos) no debe entrar en conflicto con otra operación única.

La otra es la volatilidad, cuando se espera que cambie este valor, y por qué.

Tome la primera. Si su operación en dos pasos requiere que lea el valor actual, lo modifique y lo vuelva a escribir, seguramente querrá un bloqueo, a menos que toda esta operación se pueda traducir en una sola instrucción de CPU que pueda funcionar en un única línea de caché de datos.

Sin embargo, el segundo problema es que, incluso cuando se está bloqueando, lo que verán otros subprocesos.

Un campo volatile en .NET es un campo que el compilador sabe que puede cambiar en momentos arbitrarios. En un mundo de subproceso único, el cambio de una variable es algo que sucede en algún punto de una secuencia de instrucciones secuencial, de modo que el compilador sabe cuándo ha agregado el código que lo cambia, o al menos cuando ha llamado al mundo exterior que puede o no haberlo cambiado para que, una vez que el código regrese, no tenga el mismo valor que tenía antes de la llamada.

Este conocimiento permite al compilador levantar el valor del campo en un registro una vez, antes de un bucle o bloque de código similar, y nunca volver a leer el valor del campo para ese código en particular.

Sin embargo, con multi-threading, eso podría ocasionarle algunos problemas. Un hilo puede haber ajustado el valor, y otro hilo, debido a la optimización, no leerá este valor durante un tiempo, porque sabe que no ha cambiado.

Por lo tanto, cuando marca un campo como volatile, básicamente le está diciendo al compilador que no debe suponer que tiene el valor actual de esto en ningún momento, excepto para capturar instantáneas cada vez que necesita el valor.

Las cerraduras resuelven operaciones de varios pasos, la volatilidad maneja cómo el compilador guarda en caché el valor del campo en un registro, y juntos resolverán más problemas.

También tenga en cuenta que si un campo contiene algo que no se puede leer en una sola instrucción de CPU, lo más probable es que también desee bloquear el acceso de lectura a él.

Por ejemplo, si está en una CPU de 32 bits y escribe un valor de 64 bits, esa operación de escritura requerirá dos pasos para completarse, y si otra cadena en otra CPU logra leer el 64-bit antes de que se complete el paso 2, obtendrá la mitad del valor anterior y la mitad del nuevo, muy bien mezclado, lo que puede ser incluso peor que obtener uno obsoleto.


Editar: Para contestar el comentario, que volatile garantiza la atomicidad de la operación de lectura/escritura, eso es así, es cierto, en cierto modo, por la palabra clave volatile no se puede aplicar a los campos que son mayores que De 32 bits, en efecto, hace que el campo single-cpu-instruction sea legible/escribible tanto en la CPU de 32 como en la de 64 bits.Y sí, evitará que el valor se mantenga en un registro tanto como sea posible.

De modo que parte del comentario es incorrecto, volatile no se puede aplicar a los valores de 64 bits.

Tenga en cuenta también que volatile tiene alguna semántica con respecto a la reordenación de lecturas/escrituras. Para obtener información relevante, consulte MSDN documentation o C# specification, encontrado here, sección 10.5.3.

+0

Para que quede claro, su descripción de los sonidos volátiles es más parecida a la que se encuentra en C/C++. En .net, volátil garantiza la atomicidad y la obliga a mantenerla en la memoria, en lugar de a un registro. Incluso en valores más grandes, como 64 bits. – jalf

+0

@jalf: ¿cómo se especifica la volatilidad en los valores de 64 bits en C# ??? –

1

A nivel de hardware, varias CPU nunca puede escribir simultaneamente en la misma ubicación de memoria RAM atómica. El tamaño de una operación de lectura/escritura atómica depende de la arquitectura de la CPU, pero generalmente es de 1, 2 o 4 bytes en una arquitectura de 32 bits. Sin embargo, si intenta leer el resultado, siempre existe la posibilidad de que otra CPU haya realizado una escritura en la misma ubicación de memoria RAM entremedio. En un nivel bajo, los bloqueos giratorios se usan generalmente para sincronizar el acceso a la memoria compartida. En un lenguaje de alto nivel, tales mecanismos se pueden llamar, p. regiones críticas

El tipo volátil simplemente se asegura de que la variable se escriba inmediatamente de nuevo en la memoria cuando se cambia (incluso si el valor se va a utilizar en la misma función). Un compilador generalmente mantendrá un valor en un registro interno durante el mayor tiempo posible si el valor se reutilizará más adelante en la misma función, y se almacena de nuevo en la RAM cuando se finalizan todas las modificaciones o cuando se devuelve una función. Los tipos volátiles son más útiles cuando se escribe en registros de hardware, o cuando se quiere asegurar que un valor se almacena en la memoria RAM, p. un sistema multihilo

Cuestiones relacionadas