threading thread net for book c# multithreading .net-4.0 memory-barriers

net - for thread c#



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

Supongamos que tengo un campo que controla la ejecución de algunos bucles:

private static bool shouldRun = true;

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

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

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

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

¿Es correcto mi comprensión acerca de Thread.MemoryBarrier? Dado que tengo subprocesos que pueden establecer la variable shouldRun (esto no se puede cambiar fácilmente), ¿es esta una manera razonable de asegurar que mi bucle se detenga una vez que shouldRun esté configurado en falso por cualquier subproceso?


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

No. Supongamos que un hilo establece el indicador antes de que el bucle comience a ejecutarse. El bucle todavía podría ejecutarse una vez, utilizando un valor en caché de la bandera. ¿Es eso correcto ? Ciertamente me parece incorrecto. Esperaría que si estableciera el indicador antes de la primera ejecución del bucle, que el bucle se ejecute cero veces, no una vez.

Hasta donde entiendo Thread.MemoryBarrier (), tener esta llamada dentro del bucle while evitará que mi hilo de trabajo obtenga una versión en caché de shouldRun, y evitará efectivamente que ocurra un bucle infinito. ¿Es correcto mi comprensión acerca de Thread.MemoryBarrier?

La barrera de memoria se asegurará de que el procesador no realice reordenaciones de lecturas y escrituras de tal manera que un acceso a la memoria que se encuentra lógicamente antes de que la barrera realmente se observe después de ella, y viceversa.

Si estás empeñado en hacer un código de bloqueo bajo, me inclino a hacer que el campo sea volátil en lugar de introducir una barrera de memoria explícita. "volatile" es una característica del lenguaje C #. Una característica peligrosa y mal entendida, pero una característica del lenguaje. Se comunica claramente al lector del código que el campo en cuestión se usará sin bloqueos en varios subprocesos.

¿Es esta una manera razonable de asegurar que mi bucle se detenga una vez que shouldRun esté configurado en falso por cualquier hilo?

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

Las técnicas típicas de bloqueo bajo se justifican por consideraciones de rendimiento. Hay dos consideraciones de este tipo:

Primero, un bloqueo en disputa es potencialmente extremadamente lento; se bloquea siempre que haya un código ejecutándose en el bloqueo. Si tiene un problema de rendimiento porque hay demasiada disputa, primero intentaría resolver el problema eliminando la disputa. Solo si no pudiera eliminar la disputa, recurriría a una técnica de bloqueo bajo.

En segundo lugar, podría ser que un bloqueo incontenido sea ​​demasiado lento. Si el "trabajo" que está haciendo en el bucle toma, digamos, menos de 200 nanosegundos, entonces el tiempo requerido para verificar el bloqueo incontenido (aproximadamente 20 ns) es una fracción significativa del tiempo empleado en el trabajo. En ese caso, sugiero que haga más trabajo por bucle . ¿Es realmente necesario que el bucle se detenga dentro de los 200 ns de la marca de control que se está configurando?

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

Y también, por supuesto, si está induciendo una barrera de memoria cada 200 ns o menos, también posiblemente esté arruinando el rendimiento de otras maneras. El procesador desea hacer esas optimizaciones de acceso a la memoria móvil alrededor de usted para usted; Si lo obligas a abandonar constantemente esas optimizaciones, te estás perdiendo una posible victoria.


Creo que su comprensión está un poco fuera de línea

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

Una barrera de memoria es una forma de imponer una restricción de orden en las instrucciones de lectura / escritura. Si bien los resultados de la reordenación de lectura / escritura pueden tener la apariencia de almacenar en caché, una barrera de memoria en realidad no afecta al almacenamiento en caché de ninguna manera. Simplemente actúa como una valla sobre la que las instrucciones de lectura y escritura no pueden cruzarse.

Con toda probabilidad, esto no evitará un bucle infinito. Lo que está haciendo la valla de memoria es que este escenario está forzando que todas las lecturas y escrituras que suceden en el cuerpo del bucle ocurran antes de que el ciclo condicional lea el valor de shouldRun

Wikipedia tiene un buen recorrido en la barrera de la memoria que puede ser útil.


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

Sincroniza el acceso a la memoria de la siguiente manera: el procesador que ejecuta el subproceso actual no puede reordenar las instrucciones de tal manera que los accesos a la memoria antes de la llamada a MemoryBarrier se ejecutan después de los accesos a la memoria que siguen a la llamada a MemoryBarrier.

Esto evitará que se vuelvan a ordenar las instrucciones después de que la barrera de memoria lo llame, pero eso no evitará que la programación de hilos decida que el bucle debería tener otra vuelta antes de que el hilo escritor realice la escritura, por ejemplo, no es un mecanismo de bloqueo o sincronización. Simplemente evita que se vuelvan a ordenar otras instrucciones en el bucle (como la inicialización de variables) antes de la comprobación del valor de shouldRun.

Del mismo modo, omitir esto no causará un bucle infinito; en cualquier caso, shouldRun se verificará con cada iteración. No hay "valor almacenado en caché" aquí.


En este caso Thread.MemoryBarrier(); Es una variante menos eficaz, pero no menos segura de

private static volatile readonly bool shouldRun = true;

porque la declaración de shouldRun como volatile realizará una barrera de memoria si esto es necesario para lograr una lectura actualizada, pero puede que no sea necesario hacerlo.


Esta no es una respuesta directa a la pregunta; Sin embargo, eso parece estar bien cubierto en los mensajes anteriores. Lo que no parece estar claramente establecido es cómo lograr el resultado deseado. Realmente no hay nada de malo con el uso de los objetos de sincronización de subprocesos previstos:

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 usar una declaración de bloqueo al acceder 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;