2012-01-04 11 views
19

Supongamos que tengo un campo que controla la ejecución de algunos de bucle:¿Es este un uso correcto de Thread.MemoryBarrier()?

private static bool shouldRun = true; 

Y tengo una ejecución hilo, que tiene un código como:

while(shouldRun) 
{ 
    // Do some work .... 
    Thread.MemoryBarrier(); 
} 

Ahora, otro hilo podría establecer shouldRun-false, sin usar ningún mecanismo de sincronización

Por lo que yo entiendo Thread.MemoryBarrier(), tener esta llamada dentro del ciclo while evitará que mi hilo de trabajo obtenga una versión almacenada en caché del shouldRun, y efectivamente evitará que ocurra un ciclo infinito.

¿Es correcto mi entendimiento sobre Thread.MemoryBarrier? Dado que tengo hilos que pueden establecer la variable shouldRun (esto no se puede cambiar fácilmente), ¿es esta una manera razonable de asegurar que mi ciclo se detenga una vez que shouldRun esté configurado como falso por cualquier hilo?

+0

¿No declarar el booleano como Do Volátil ¿la misma cosa? No estoy seguro de Thread.MemoryBarrier por lo tanto, comentar no responder. p.ej. http://www.albahari.com/threading/part4.aspxAnother> "una forma más avanzada de resolver este problema es aplicar la palabra clave volátil al campo _completa. La palabra clave volátil instruye al compilador a generar una valla de adquisición en cada lea de ese campo, y una valla de liberación en cada escritura en ese campo ". Atentamente, –

+3

Volátil es algo muy malo y no debe usarse: http://blogs.msdn.com/b/ericlippert/archive/2011/06/16/atomicity-volatility-and-immutability-are-different-part -three.aspx – Steven

+1

¡Lol, no era consciente de eso! Como programador ex C/ASM, pensé que era algo bueno :) Gracias por el enlace, voy a echar un vistazo. –

Respuesta

26

Es este es un uso correcto de Thread.MemoryBarrier()?

No. Supongamos un hilo establece el indicador antes el bucle incluso comienza a ejecutar. El ciclo aún podría ejecutarse una vez, utilizando un valor en caché de la bandera. ¿Es eso correcto? Ciertamente me parece incorrecto. Me gustaría esperar que si establezco el indicador antes de la primera ejecución del ciclo, el ciclo se ejecute cero veces, no una vez.

Por lo que yo entiendo Thread.MemoryBarrier(), teniendo esta llamada dentro del bucle, mientras que impedirá que mi hilo de trabajo de conseguir una versión en caché de la shouldRun, y prevenir de manera efectiva un bucle infinito suceda. ¿Es correcto mi entendimiento sobre Thread.MemoryBarrier?

La barrera de la memoria se asegurará de que el procesador no hace ningún reordenamientos de lectura y escritura de manera que un acceso a la memoria que es lógicamente antes la barrera es en realidad observa que es después, y vice versa.

Si está empeñado en hacer código de bloqueo bajo, me inclinaría a hacer que el campo sea volátil en lugar de introducir una barrera de memoria explícita. "volátil" es una característica del lenguaje C#. Una característica peligrosa y poco conocida, pero una característica del lenguaje. Comunica claramente al lector del código que el campo en cuestión va a ser utilizado sin bloqueos en múltiples hilos.

¿es esta una manera razonable de garantizar que mi ciclo se detenga una vez que se haya establecido que la ejecución es falsa por algún hilo?

Algunas personas lo consideran razonable. No haría esto en mi propio código sin una razón muy, muy buena.

Por lo general, las técnicas de bloqueo bajo se justifican por consideraciones de rendimiento. Hay dos consideraciones de este tipo:

En primer lugar, un bloqueo disputado es potencialmente extremadamente lento; bloquea siempre que haya código ejecutándose en la cerradura. Si tiene un problema de rendimiento porque hay demasiada contención, primero trataría de resolver el problema eliminando la disputa. Solo si no pudiera eliminar la disputa, ¿usaría una técnica de bloqueo bajo?

En segundo lugar, podría ser que un bloqueo no previsto sea demasiado lento. Si el "trabajo" que está haciendo en el ciclo lleva, digamos, menos de 200 nanosegundos, entonces el tiempo requerido para verificar el bloqueo no previsto -alrededor de 20 ns- es una fracción significativa del tiempo dedicado a hacer el trabajo. En ese caso, le sugiero que haga más trabajo por ciclo. ¿Es realmente necesario que el ciclo se detenga dentro de 200 ns de la bandera de control que se está configurando?

Solo en los escenarios de rendimiento más extremos imagino que el costo de verificar un bloqueo no previsto es una fracción significativa del tiempo que se pasa en el programa.

Y también, por supuesto, si está induciendo una barrera de memoria cada 200 ns o más, también es posible que arruine el rendimiento de otras maneras. El procesador quiere para realizar esas optimizaciones de acceso-memoria-memoria-en-tiempo para usted; si lo obligas a abandonar constantemente esas optimizaciones, te estás perdiendo una posible victoria.

2

En este caso Thread.MemoryBarrier(); es una variante menos seguro menos eficiente, pero no de

private static volatile readonly bool shouldRun = true; 

porque la declaración de shouldRun tan volátil realizará una barrera de memoria si esto es necesario para lograr una puesta al día lee, pero puede que no necesite hacerlo.

3

Dependiendo de lo que esté tratando de hacer, esto probablemente no hará lo que espera. De MSDN, MemoryBarrier:

Sincroniza acceso a la memoria de la siguiente manera: El procesador que ejecuta el hilo actual no puede cambiar el orden de las instrucciones de tal manera que la memoria accede antes de la llamada a MemoryBarrier ejecutar después de la memoria accesos que seguir la llamada a MemoryBarrier.

Esto evitará reordenamiento de instrucciones después de la llamada barrera de memoria al que conoce, pero eso no impedirá que la programación de subprocesos de decidir que el bucle debe tener otra dar la vuelta antes de que el subproceso de escritura en realidad realiza la escritura, por ejemplo, no es un mecanismo de bloqueo o sincronización. Solo evita que se reordenen otras instrucciones en el ciclo (como la inicialización de la variable) antes de el valor de shouldRun.

Del mismo modo, dejar esto fuera no causará un bucle infinito en ningún caso, shouldRun se comprobará con cada iteración. No hay un "valor en caché" aquí.

+0

Siempre que no se requiera una sincronización específica en cuanto a _cuando_ el hilo ve que shouldRun se vuelve falso, no hay necesidad de ningún tipo de cercado. Se observará que el valor del campo cambiará eventualmente; la valla es solo un problema si existe una orden/observabilidad requerida particular de lecturas y escrituras para que ocurra el comportamiento correcto. –

7

Creo que su comprensión es un poco fuera de esta línea

Por lo que yo entiendo Thread.MemoryBarrier(), teniendo esta llamada dentro del bucle while evitará que mi hilo de trabajo de conseguir una versión en caché the shouldRun, y que previene efectivamente que ocurra un ciclo infinito.

Una barrera de memoria es una forma de aplicar una restricción de ordenamiento en las instrucciones de lectura/escritura. Si bien los resultados del reordenamiento de lectura/escritura pueden tener la apariencia de almacenamiento en caché, una barrera de memoria no afecta el almacenamiento en caché de ninguna manera.Simplemente actúa como una valla sobre la cual las instrucciones de lectura y escritura no se pueden cruzar.

Con toda probabilidad, esto no evitará un ciclo infinito. Lo que la valla de memoria está haciendo es este escenario está obligando a todas las lecturas y escrituras que ocurren en el cuerpo del bucle que se produzca antes de que el valor de shouldRun es leído por el bucle condicional

Wikipedia has a nice walk through on memory barrier that you may find useful

3

Esta no es una respuesta directa a la pregunta; sin embargo, eso parece estar bien cubierto en las publicaciones anteriores. Lo que no parece estar claramente establecido es cómo lograr el resultado deseado. Realmente no hay nada malo con sólo el uso de los objetos de sincronización de hilos destinados:

private static readonly ManualResetEvent shutdownEvent = new ManualResetEvent(false); 

//Thread 1 - continue until event is signaled 
while(!shutodwnEvent.WaitOne(0)) 
{ 
    // Do some work .... 
    Thread.MemoryBarrier(); 
} 

//Thread 2 - signal the other thread 
shutdownEvent.Set(); 

El otro enfoque es el uso de una declaración de bloqueo cuando se accede a la variable:

private static object syncRoot = new Object(); 
private static bool shouldRun = true; 

//Thread 1 
bool bContinue; 
lock(syncRoot) 
    bContinue = shouldRun; 

while(bContinue) 
{ 
    // Do some work .... 
    lock(syncRoot) 
     bContinue = shouldRun; 
} 

//Thread 2 
lock(syncRoot) 
    shouldRun = false;