2008-09-25 26 views
87

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

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

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

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

Respuesta

97

¡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 compila cuidadosamente las solicitudes de campos estáticos ...y hago ejemplo, con no estática uno:

Si ejecuta este ejemplo en Release modo y sin depurador (es decir, utilizando Ctrl + F5), entonces la línea de while (test.foo != 255) se optimizará a 'while (true)' y este programa nunca regresa Pero después de agregar la palabra 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"); 
    } 
} 
+5

Probado en .NET 4.0, tanto x86 como x64: pueden confirmar que el ejemplo sigue siendo aplicable. ¡Gracias! :) –

+1

¡Eso es bueno!Nunca supe que el JIT realiza este tipo de optimización y que volátil lo desactiva. – usr

+3

Solo para ser explícito, está agregando la palabra clave "volátil" a la declaración de foo, ¿verdad? – JoeCool

21

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 en la palabra clave volátil es this blog post by Joe Duffy. Tenga en cuenta que él dice que la documentación de MSDN es "altamente engañosa".

+8

http://igoro.com/archive/volatile-keyword-in-c-memory-model-explained/ – Xaqron

+1

seguimiento por Joe Duffy en la palabra clave 'volátil'. http://www.bluebytesoftware.com/blog/2010/12/04/SayonaraVolatile.aspx –

6

No es realmente una cuestión de error que ocurre cuando la palabra clave 'volátil' no se especifica, 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 compilador.

La manera más fácil de pensarlo sería que el compilador 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 compilador que el valor puede cambiar (incluso si el compilador no lo cree). Esto significa que el compilador no debe tener valores en línea, mantener el caché o leer el valor anticipadamente (en un intento de optimización).

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

MSDN tiene una breve descripción here. Aquí es tal vez un puesto más en profundidad sobre los temas de Volatility, Atomicity and Interlocking

4

Es difícil de demostrar en C#, ya que el código es abstraído por una máquina virtual, por tanto, en una puesta en práctica de esta máquina que funcione bien sin volátil, mientras podría fallar en otro.

The Wikipedia has a good example how to demonstrate it in C, though.

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

Another example is Busy Waiting.

Una vez más, esto podría suceder con C#, así, pero depende en gran medida de la máquina virtual y en el compilador JIT (o intérprete, si no tiene JIT ... en teoría, creo EM siempre usa un compilador JIT y también Mono usa uno, pero es posible que pueda deshabilitarlo manualmente).

4

aquí está mi contribución a la comprensión colectiva de este comportamiento ... No es mucho, sólo una demostración (basado en el demo de xkip), que muestra el comportamiento de una volátil versos no volátil (es decir, "normal") int value, side-by-side, en el mismo programa ... 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(); 
    } 

    } 
} 

Existen algunas explicaciones sucintas muy excelentes, así como algunas referencias excelentes. Gracias a todos por ayudarme a entender volatile (al menos lo suficiente como para saber que no confío en volatile donde mi primer instinto fue lock).

Saludos, y gracias por TODO el pez. Keith.


PS: Estaría muy interesado en una demostración de la solicitud original, que era: "Me gustaría ver un estática volátil int comportarse de manera adecuada cuando un estática int se porta mal.

he usado sin éxito este reto. (En realidad me dio bastante rápido ;-). En todo lo que probé con vars estáticos que se comportan de manera "correcta", independientemente de si están o no volátil ... y me encantaría una explicación de POR QUÉ ese es el caso, si es así ... Es que el compilador no almacena en caché los valores de variables estáticas 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 nueva pregunta ... es un intento de superar la comunidad a la pregunta original.

2

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

I agarró un ejemplo del texto anterior que alteré un poco, mediante la creación de un campo volátil estática. Cuando elimine la palabra clave volatile, el programa se bloqueará indefinidamente. Ejecute este ejemplo en el modo Release.

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 
    } 
} 
Cuestiones relacionadas