Ilustrando el uso de la palabra clave volátil en C #

Me gustaría codificar un pequeño progtwig que ilustra visualmente el comportamiento de la palabra clave volatile . Idealmente, debería ser un progtwig que realiza el acceso concurrente a un campo estático no volátil y que obtiene un comportamiento incorrecto debido a eso.

Agregar la palabra clave volátil en el mismo progtwig debería solucionar el problema.

Ese algo que no logré lograr. Incluso intentándolo varias veces, habilitando la optimización, etc., siempre obtengo un comportamiento correcto sin la palabra clave ‘volátil’.

¿Tienes alguna idea sobre este tema? ¿Sabes cómo simular un problema así en una aplicación de demostración simple? ¿Depende del hardware?

¡He logrado un ejemplo de trabajo!

La idea principal recibida de wiki, pero con algunos cambios para C #. El artículo de wiki demuestra esto para el campo estático de C ++, parece que C # siempre comstack cuidadosamente las solicitudes a campos estáticos … y hago un ejemplo con uno no estático:

Si ejecuta este ejemplo en modo Release y sin depuración (es decir, usando Ctrl + F5), entonces la línea while (test.foo != 255) se optimizará a ‘while (true)’ y este progtwig nunca regresa. Pero después de agregar palabras clave volatile , siempre obtiene ‘OK’.

 class Test { /*volatile*/ int foo; static void Main() { var test = new Test(); new Thread(delegate() { Thread.Sleep(500); test.foo = 255; }).Start(); while (test.foo != 255) ; Console.WriteLine("OK"); } } 

Sí, depende del hardware (es poco probable que vea el problema sin múltiples procesadores), pero también depende de la implementación. Las especificaciones del modelo de memoria en la especificación CLR permiten cosas que la implementación de Microsoft de la CLR no necesariamente hace. La mejor documentación que he visto sobre la palabra clave volátil es esta publicación de blog de Joe Duffy . Tenga en cuenta que él dice que la documentación de MSDN es “altamente engañosa”.

En realidad, no se trata de una falla que ocurre cuando no se especifica la palabra clave ‘volátil’, más que un error podría ocurrir cuando no se ha especificado. En general, sabrá cuándo es este el caso mejor que el comstackdor.

La manera más fácil de pensarlo sería que el comstackdor podría, si así lo desea, poner en línea ciertos valores. Al marcar el valor como volátil, se está diciendo a sí mismo y al comstackdor que el valor puede cambiar (incluso si el comstackdor no lo cree). Esto significa que el comstackdor no debe tener valores en línea, mantener el caché o leer el valor anticipadamente (en un bash de optimización).

Este comportamiento no es realmente la misma palabra clave que en C ++.

MSDN tiene una breve descripción aquí . Aquí hay una publicación quizás más profunda sobre los temas de Volatilidad, Atomicidad y Enclavamiento

Es difícil de demostrar en C #, ya que el código es abstraído por una máquina virtual, por lo tanto, en una implementación de esta máquina funciona correctamente sin volátiles, mientras que podría fallar en otra.

La Wikipedia tiene un buen ejemplo de cómo demostrarlo en C, sin embargo.

Lo mismo podría suceder en C # si el comstackdor JIT decide que el valor de la variable no puede cambiar de todos modos y, por lo tanto, crea un código de máquina que ya no lo verifica. Si ahora otro hilo estaba cambiando el valor, su primer hilo aún podría quedar atrapado en el ciclo.

Otro ejemplo es Ocupado esperando.

De nuevo, esto también podría pasar con C #, pero depende mucho de la máquina virtual y del comstackdor JIT (o del intérprete, si no tiene JIT … en teoría, creo que MS siempre usa un comstackdor JIT y también Mono usa uno, pero es posible que puedas desactivarlo manualmente).

Aquí está mi contribución al entendimiento colectivo de este comportamiento … No es mucho, solo una demostración (basada en la demostración de xkip) que muestra el comportamiento de un volátil versos de un valor int no volátil (es decir, “normal”), de lado a lado. -side, en el mismo progtwig … que es lo que estaba buscando cuando encontré este hilo.

 using System; using System.Threading; namespace VolatileTest { class VolatileTest { private volatile int _volatileInt; public void Run() { new Thread(delegate() { Thread.Sleep(500); _volatileInt = 1; }).Start(); while ( _volatileInt != 1 ) ; // Do nothing Console.WriteLine("_volatileInt="+_volatileInt); } } class NormalTest { private int _normalInt; public void Run() { new Thread(delegate() { Thread.Sleep(500); _normalInt = 1; }).Start(); // NOTE: Program hangs here in Release mode only (not Debug mode). // See: http://stackoverflow.com/questions/133270/illustrating-usage-of-the-volatile-keyword-in-c-sharp // for an explanation of why. The short answer is because the // compiler optimisation caches _normalInt on a register, so // it never re-reads the value of the _normalInt variable, so // it never sees the modified value. Ergo: while ( true )!!!! while ( _normalInt != 1 ) ; // Do nothing Console.WriteLine("_normalInt="+_normalInt); } } class Program { static void Main() { #if DEBUG Console.WriteLine("You must run this program in Release mode to reproduce the problem!"); #endif new VolatileTest().Run(); Console.WriteLine("This program will now hang!"); new NormalTest().Run(); } } } 

Hay algunas explicaciones sucintas realmente excelentes, así como algunas referencias excelentes. Gracias a todos por ayudarme a dar vueltas a mi cabeza volatile (al menos lo suficiente como para saber que no confío en la volatile donde mi primer instinto fue lock ).

Saludos, y gracias por TODO el pez. Keith.


PD: Me interesaría mucho una demostración de la solicitud original, que era: “Me gustaría ver que una int volátil estática se comporte correctamente donde una int estática se comporta mal.

He intentado y fallé este desafío. (En realidad, me di por vencido bastante rápido ;-). En todo lo que probé con los vars estáticos se comportan “correctamente” independientemente de si son volátiles o no … y me encantaría una explicación de POR QUÉ ese es el caso, si es que es así … ¿Es eso? el comstackdor no almacena en caché los valores de vars estáticos en los registros (es decir, almacena en caché una referencia a esa dirección de montón en su lugar)?

No, esta no es una pregunta nueva … es un bash de hacer que la comunidad vuelva a la pregunta original.

Encontré el siguiente texto de Joe Albahari que me ayudó mucho.

  • Barreras de memoria y volatilidad

Tomé un ejemplo del texto anterior que modifiqué un poco, al crear un campo volátil estático. Cuando elimine la palabra clave volatile el progtwig se bloqueará indefinidamente. Ejecute este ejemplo en el modo de lanzamiento .

 class Program { public static volatile bool complete = false; private static void Main() { var t = new Thread(() => { bool toggle = false; while (!complete) toggle = !toggle; }); t.Start(); Thread.Sleep(1000); //let the other thread spin up complete = true; t.Join(); // Blocks indefinitely when you remove volatile } }