2009-08-25 9 views

Respuesta

20

Usted usa volatile/Thread.MemoryBarrier() cuando quiere acceder a una variable entre subprocesos sin bloquear.

Las variables que son atómicas, como por ejemplo int, siempre se leen y escriben completas a la vez. Eso significa que nunca obtendrás la mitad del valor antes de que otro hilo lo cambie y la otra mitad después de que haya cambiado. Por eso, puede leer y escribir de forma segura el valor en diferentes hilos sin sincronizar.

Sin embargo, el compilador puede optimizar algunas lecturas y escrituras, lo que previene con la palabra clave volatile.Si, por ejemplo, tiene un bucle de esta manera:

sum = 0; 
foreach (int value in list) { 
    sum += value; 
} 

El compilador puede realmente hacer los cálculos en un registro del procesador y sólo escribir el valor de la variable sum después del bucle. Si crea la variable sumvolatile, el compilador generará un código que leerá y escribirá la variable para cada cambio, de modo que su valor esté actualizado en todo el ciclo.

+1

+1, pero ¿en qué escenarios importa que el compilador genere código que lea y escriba la variable para cada cambio? – dtb

+0

Eso se desea principalmente al enhebrar dtb, si no un hilo puede tener un valor diferente al del otro y pueden ocurrir todo tipo de locuras. – Skurmedel

+0

Bueno, eso o usa una barrera de memoria. – Skurmedel

10

¿Qué hay de malo en

private static readonly object syncObj = new object(); 
private static int counter; 

public static int NextValue() 
{ 
    lock (syncObj) 
    { 
     return counter++; 
    } 
} 

?

Esto hace todo el bloqueo necesario, barreras de memoria, etc. para usted. Está bien entendido y es más legible que cualquier código de sincronización personalizado basado en volatile y Thread.MemoryBarrier().


EDITAR

No puedo pensar en un escenario en el que me gustaría usar volatile o Thread.MemoryBarrier(). Por ejemplo

private static volatile int counter; 

public static int NextValue() 
{ 
    return counter++; 
} 

es no equivalente al código de arriba y es no thread-safe (volatile no hace ++ mágicamente convertido thread-safe).

En un caso como este:

private static volatile bool done; 

void Thread1() 
{ 
    while (!done) 
    { 
     // do work 
    } 
} 

void Thread2() 
{ 
    // do work 
    done = true; 
} 

(que deberían funcionar) que haría uso de un ManualResetEvent para indicar cuándo se realiza Thread2.

+0

Nada 'incorrecto' con su código. Estoy tratando de comprender las circunstancias en las que se aplican los volatiles y MemoryBarrier. Como en la respuesta de Jon Skeet aquí: http://stackoverflow.com/questions/395232/we-need-to-lock-a-net-int32-when-reading-it-in-a-multithreaded-code. – Alex

+0

.net también tiene un modelo de memoria bastante estricto, como leer o escribir no puede seguir otra escritura. –

+4

Aunque la especificación ECMA tiene un modelo de memoria bastante débil, por lo que es posible que desee tener cuidado con eso, consulte http://www.bluebytesoftware.com/blog/2007/11/10/CLR20MemoryModel.aspx y http: // blogs .msdn.com/cbrumme/archive/2003/05/17/51445.aspx –

10

Básicamente, si está utilizando cualquier otro tipo de sincronización para hacer que su código sea enhebrable, entonces no es necesario.

La mayoría de los mecanismos de bloqueo (incluido el bloqueo) implican automáticamente una barrera de memoria para que múltiples procesadores puedan obtener la información correcta.

Volatile y MemoryBarrier se utilizan principalmente en escenarios sin bloqueo en los que se intenta evitar la penalización de rendimiento del bloqueo.

Editar : Debe leer this article por Joe Duffy sobre el modelo de memoria CLR 2.0, que aclara muchas cosas (si está realmente interesado debe leer toda el artículo de Joe Duffie que es el más grande de persona experta en el paralelismo en .NET)

+0

+1, pero así está absolutamente claro. Si haces lo que hace dtb, no es necesario volátil/Thread.MemoryBarrier. También hay situaciones en las que la volatilidad sola en un miembro no es suficiente. – Skurmedel

0

Como el nombre implica garantías volátiles, el valor de caché se vacía en la memoria para que todos los subprocesos vean el mismo valor. Por ejemplo, si tengo un número entero cuya última escritura se guarda en el caché, es posible que otros hilos no lo vean. Incluso pueden ver su copia en caché de ese entero. Marcar una variable como volátil hace que se lea directamente desde la memoria.

Sriwantha Sri Aravinda Attanayake

Cuestiones relacionadas