2012-04-25 4 views
9

Tengo un hilo que gira hasta que un int modificado por otro hilo tiene un cierto valor.¿Necesito que este campo sea volátil?

int cur = this.m_cur; 
while (cur > this.Max) 
{ 
    // spin until cur is <= max 
    cur = this.m_cur; 
} 

¿Es necesario declarar este.m_curto como volátil para que esto funcione? ¿Es posible que esto gire para siempre debido a la optimización del compilador?

+9

Haga del int una propiedad y señale el hilo, (autoResetEvent, quizás), en el método setter. Se elude el problema, se reduce el uso de la CPU y se elimina la duda volátil. –

+0

Esto suele ser una mala idea, excepto en algunos casos excepcionales; ¿sabe por casualidad cuántos microsegundos desea esperar? –

+0

El bucle de CPU mientras se lee 'cur' escrito por otro hilo no detectará cur out of limit si el hilo de sondeo no se está ejecutando cuando el hilo del setter escribe un valor fuera de límite. Si ha sido reemplazado en una caja sobrecargada, tendrá que esperar un medio cuántico medio antes de detectar la curva fuera de rango.Si cur regresa nuevamente al rango mientras el sondeo no se está ejecutando, no detectará en absoluto la condición fuera de rango. –

Respuesta

12

Sí, es un requisito difícil. El compilador just-in-time puede almacenar el valor de m_cur en un registro de procesador sin actualizarlo de la memoria. De hecho, la fluctuación de fase x86 sí lo hace, la fluctuación de fase x64 no (al menos la última vez que la miré).

La palabra clave volátil es necesaria para suprimir esta optimización.

Volatile significa algo completamente diferente en los núcleos Itanium, un procesador con un modelo de memoria débil. Desafortunadamente eso es lo que lo convirtió en la biblioteca de MSDN y la especificación del lenguaje C#. Lo que va a significar en un núcleo de ARM aún está por verse.

+4

Me encanta cómo respondiste a su pregunta, en lugar de solo decir "hazlo de otra manera" como a muchos les gusta hacer. Dependiendo de sus necesidades, una señal puede ser mucho más eficiente, pero su pregunta no fue "... ¿o qué es mejor?" Además, estoy seguro de que muchos de los usuarios aprendieron algo nuevo aquí. Gracias. – payo

+0

+1: Por cierto, ¿estamos seguros de que las propiedades impiden que el compilador JIT realice la optimización de elevación? La propiedad sería simple, por lo que probablemente se inline primero. Supongo que es posible hacer una optimización de 2 fases: elevación en línea. No he visto nada en las especificaciones que impida eso, pero tal vez no me veía tan duro. –

+1

@Brian - Lo descubrí al desconcertarme acerca de las propiedades que se usan en los casos en que se requeriría volátiles pero sin ninguna sincronización. BackgroundWorker.CancellationPending es un buen ejemplo, un bool. Esto no se describe en ningún lugar que conozca, la semántica de * volátil * está muy mal documentada. Siempre lo han sido, antes de .NET también. –

4

El siguiente blog tiene algunos detalles fascinantes sobre el modelo de memoria en C#. En resumen, parece más seguro usar la palabra clave volátil.

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

Desde el blog a continuación

class Test 
{ 
    private bool _loop = true; 

    public static void Main() 
    { 
     Test test1 = new Test(); 

     // Set _loop to false on another thread 
     new Thread(() => { test1._loop = false;}).Start(); 

     // Poll the _loop field until it is set to false 
     while (test1._loop == true) ; 

     // The loop above will never terminate! 
    } 
} 

Hay dos maneras posibles de conseguir el bucle while para terminar: Utilice una cerradura para proteger todos los accesos (lectura y escritura) a la _loop field Marque el campo _loop como volátil Hay dos razones por las que una lectura de un campo no volátil puede observar un valor obsoleto: optimizaciones del compilador y optimizaciones del procesador.

+1

Parece incluso más seguro no utilizar ningún ciclo de sondeo. –

+0

Verdad para la mayoría de los casos. Aunque me imagino que también hay razones para girar el bloqueo, como evitar el cambio de contexto de ceder un hilo. –

+0

El problema de los conmutadores de contexto de avoding es que no siempre es posible. Además, el código OP no es un spinlock de bandera booleana clsssic. Si la caja está sobrecargada porque hay más hilos listos que núcleos, la secuencia de sondeo puede ser sustituida y no ejecutarse por un tiempo. Durante este tiempo, no puede detectar 'cur' fuera de límite. Si cur regresa dentro del límite antes de que se ejecute el sondeo, la condición de fuera de límite se ha perdido por completo. –

0

Depende de cómo se modifique m_cur. Si está utilizando una declaración de asignación normal como m_cur--;, entonces necesita ser volátil. Sin embargo, si se está modificando mediante una de las operaciones Interlocked, no es así porque los métodos de Interlocked insertan automáticamente una barrera de memoria para garantizar que todos los hilos obtengan la nota.

En general, usar enclavamiento para modificar los valores atómicos que se comparten entre subprocesos es la opción preferible. No solo se ocupa de la barrera de la memoria, sino que también tiende a ser un poco más rápido que otras opciones de sincronización.

Dicho esto, al igual que otros, han dicho que los lazos de votación son un enorme desperdicio. Sería mejor detener el hilo que debe esperar, y dejar que quien esté modificando m_cur se haga cargo de despertarlo cuando llegue el momento. Tanto Monitor.Wait() and Monitor.Pulse() como AutoResetEvent pueden ser adecuados para la tarea, dependiendo de sus necesidades específicas.

+0

La solución de bucle de sondeo no funciona de manera confiable, al menos, no en una caja sobrecargada. El hilo del colocador puede escribir un 'cur' fuera de rango mientras el sondeador no se está ejecutando. Si 'cur' vuelve a estar dentro del rango antes de que se ejecute el sondeo, la condición fuera de rango no se detecta en absoluto. –

Cuestiones relacionadas