2010-11-24 21 views
9

para implementar un bloqueo de código libre para multithreading aplicación Solía ​​volatile variables, Teóricamente: La palabra clave volatile se utiliza simplemente para asegurarse de que todos los hilos ver el valor más actualizada de una variable volátil; así que si el hilo A actualiza el valor de la variable y el hilo B lee esa variable justo después de que se haya realizado la actualización verá el valor más actualizado escrito recientemente desde el hilo A. Cuando leo en un C# 4.0 en Nutshell libro que esto es incorrecto porquevolátil y Thread.MemoryBarrier en C#

aplicar volátil no impide que una escritura seguido de una lectura de ser intercambiado.

Podría este problema a resolver poniendo Thread.MemoryBarrier() antes de cada get de la variable volatile como:

private volatile bool _foo = false; 

private void A() 
{ 
    //… 
    Thread.MemoryBarrier(); 
    if (_foo) 
    { 
     //do somthing 
    } 
} 

private void B() 
{ 
    //… 
    _foo = true; 
    //… 
} 

Y si esto resuelve el problema; consideramos que tenemos un ciclo while que depende de ese valor en una de sus condiciones; está poniendo Thread.MemoryBarrier() antes del ciclo while es una forma correcta de solucionar el problema? ejemplo:

private void A() 
{ 
    Thread.MemoryBarrier(); 
    while (_someOtherConditions && _foo) 
    { 
     // do somthing. 
    } 
} 

Para ser más exacto que quiero la variable _foo para dar su valor más fresco cuando cualquier tema preguntando por él en cualquier momento; por lo que si la inserción Thread.MemoryBarrier() antes de llamar a la variable fija el problema, entonces podría utilizar Foo propiedad en lugar de _foo y hacer un Thread.MemoryBarrier() dentro de la escapada de esa propiedad como:

Foo 
{ 
    get 
    { 
     Thread.MemoryBarrier(); 
     return _foo; 
    } 
    set 
    { 
     _foo = value; 
    } 
} 
+0

Posible duplicar ... http: //stackoverflow.com/questions/1330590/when-to-use-volatile-or-thread-memorybarrier-in-threadsafe-locking-code-c –

+0

@Aron; no, no está duplicado; esta es otra pregunta. –

+0

@Jalal ¿Estás tratando de resolver un problema usando este enfoque volátil/memorybarrier o estás bien con cualquier enfoque que resuelva tu problema? –

Respuesta

8

El "C# In a Nutshell" es correcto, pero su declaración es irrelevante. ¿Por qué?

  • A 'escritura' seguido de un 'leer', withot 'volátil', se garantiza que se produzca en el programa de orden de todos modos si efectúa lógica dentro de un solo hilo
  • El 'escribir' antes de una 'leer' en un programa multiproceso es completamente inútil de qué preocuparse en su ejemplo.

Aclaremos. Tome su código original:

private void A() 
{ 
    //… 
    if (_foo) 
    { 
     //do something 
    } 
} 

¿Qué pasa si el programador de subprocesos ya ha comprobado la variable _foo, pero se suspendió justo antes del comentario //do something? Bueno, en ese punto su otro hilo podría cambiar el valor de _foo, lo que significa que todos sus componentes volátiles y Thread.MemoryBarriers contaron en vano !!! Si es absolutamente esencial que se evite el do_something si el valor de _foo es falso, entonces no tiene más remedio que usar un candado.

Sin embargo, si está bien que el do something se esté ejecutando cuando de repente _foo se convierte en falso, significa que la palabra clave volátil fue más que suficiente para sus necesidades.

Para ser claros: todos los que respondieron que le dicen que use una barrera de memoria son incorrectos o están dando excesos.

+0

gracias por su respuesta; En mi caso, quiero evitar que se ejecute do_something si el _foo fue verdadero tanto como sea posible, pero ** no es mortalmente crítico **; pero si pudiera hacerlo más preciso con MemoryBarrier entonces ** por qué no ** úselo con el volátil .. Si el horario del hilo suspende el hilo después de que el _foo verifique "la bruja es posible" entonces el do_somthing se ejecutará pero al menos ** Reducimos las posibilidades ** de que el _foo sea falso en ese momento ¿verdad? –

+2

@Jalal: la declaración 'volátil' ya garantiza, por sí misma, que la variable no se almacenará en un registro de la CPU. Garantiza la lectura y escritura directa de memoria (lo que invalida la memoria caché de otros procesadores en una máquina multiprocesador). Por lo tanto, la barrera de memoria explícita es innecesaria; el 'volátil' ya está logrando lo que necesita. –

+0

@BrentArias MemoryBarrier * es * necesario porque es posible que el procesador actualice el valor en un procesador y no en el otro, por lo que un hilo lee un valor actualizado y el otro hilo lee un valor obsoleto. – Anthony

0

En el segundo ejemplo, se necesitaría poner también un Thread.MemoryBarrier(); dentro del ciclo, para asegurarse de obtener el valor más reciente cada vez que verifique la condición del ciclo.

+0

si esto solucionó el problema, entonces poner Thread.MemoryBarrier() debería estar en la última línea antes de cerrar el paréntesis del bucle while y antes de la llamada del bucle while ... –

+0

@Jalal: estoy bastante seguro de que el 'MemoryBarrier 'explícito 'no se requieren llamadas en estos ejemplos, siempre que' _foo' esté marcado como 'volátil'. (Aunque no soy un experto, probablemente usaría un 'candado'.) – LukeH

+2

En este caso: el uso de Thread.MemoryBarrier, ya sea antes, después o ambos, no está logrando nada que no sea volátil. . –

0

sacado de here ...

class Foo 
{ 
    int _answer; 
    bool _complete; 

    void A() 
    { 
    _answer = 123; 
    Thread.MemoryBarrier(); // Barrier 1 
    _complete = true; 
    Thread.MemoryBarrier(); // Barrier 2 
    } 

    void B() 
    { 
    Thread.MemoryBarrier(); // Barrier 3 
    if (_complete) 
    { 
     Thread.MemoryBarrier();  // Barrier 4 
     Console.WriteLine (_answer); 
    } 
    } 
} 

Barreras 1 y 4 evitar este ejemplo de escribir “0”. Las barreras 2 y 3 proporcionan una garantía de frescura: garantizan que si B se ejecutó después de A, la lectura de _completa sería verdadera.

Así que si volvemos al ejemplo de su bucle ... así es como debería ser ...

propias palabras
private void A() 
{ 
    Thread.MemoryBarrier(); 
    while (_someOtherConditions && _foo) 
    { 
     //do somthing 
     Thread.MemoryBarrier(); 
    } 
} 
+1

@ Aaron Gracias, compruebo ese enlace antes de que esté en el mismo libro "C# 4.0 en una cáscara de nuez" ... sin embargo, si esto solucionó el problema, poner Thread.MemoryBarrier() debería estar en la última línea antes de cerrar el bucle while . –

+0

@Jalal Depende de lo que haga su "hacer algo", donde el ThreadBarrier se sienta ahora afecta a su _otras condiciones/_foo vars –

+1

@ Aaron: Si '_foo' está marcado como' volátil', entonces estoy bastante seguro de que el 'MemoryBarrier' explícito las llamadas son innecesarias en estos ejemplos particulares. (Bastante seguro, pero no estoy 100% seguro, y en caso de duda, probablemente solo use un 'candado'.) – LukeH

0

de Microsoft sobre las barreras de memoria: Se requiere

MemoryBarrier sólo en sistemas multiprocesador con orden de memoria débil (por ejemplo, un sistema que emplea múltiples procesadores Intel Itanium).

Para la mayoría de los propósitos, la instrucción de bloqueo C#, la instrucción SyncLock de Visual Basic o la clase Monitor proporcionan formas más fáciles de sincronizar datos.

+0

Consulte http://www.albahari.com/threading/part4.aspx para ver que las palabras de Microsoft no son correctas. –

+0

El póster solicita específicamente una implementación sin bloqueo. –

5

El libro es correcto.
El modelo de memoria del CLR indica que las operaciones de carga y almacenamiento pueden reordenarse. Esto se aplica a las variables volátiles y no volátiles.

Declarar una variable como volatile sólo significa que las operaciones de carga tendrán adquieren semántica y operaciones de la tienda tendrán liberación semántica. Además, el compilador evitará realizar ciertas optimizaciones que se transmiten por el hecho de que se accede a la variable de manera serializada y con un único subproceso (por ejemplo, carga/almacenamiento de bucles).

El uso de la palabra clave volatile solo no crea secciones críticas, y no hace que los subprocesos se sincronicen mágicamente entre sí.

Debe ser extremadamente cuidado al escribir el código de bloqueo. No hay nada simple al respecto, e incluso los expertos tienen problemas para hacerlo bien.
Cualquiera que sea el problema original que está tratando de resolver, es probable que haya una forma mucho más razonable de hacerlo.

+0

@Liran: sabía que usar solo el elemento volátil no crea secciones críticas ni causa que el hilo se sincronice entre sí ... Solo estoy pidiendo que la variable _foo sea un valor actualizado cuando un hilo lo pida en cualquier momento dentro del código de bloqueo libre. –

+0

@Jalal, declarar la variable como volátil garantizará que cada operación de carga leerá el valor más actualizado de la variable (por ejemplo, desde la memoria principal en lugar de un registro). No debe extender barreras de memoria en todo su código ... hacerlo podría tener un efecto negativo importante en el rendimiento. Una vez más, si no solo está tratando de jugar y experimentar con el código de bloqueo, y hay un código de producción real en la imagen ... lo aliento a que encuentre otra solución. – Liran

+0

@Liran; "declarar la variable como volátil garantizará que cada operación de carga leerá el valor más actualizado de la variable"; como lo entendí del libro http://www.albahari.com/threading/part4.aspx que wirte follwed por read quizás no obtenga el valor más actualizado, y sí, estoy intentando experimentar con el código de bloqueo; la pregunta es: ¿es un gran efecto negativo en el rendimiento de la aplicación cuando hago un 'Thread.MemoryBarrier' antes de cualquier operación get de la variable volátil, si no podría ponerlo en una propiedad antes de cualquier obtención de la variable.Consulte la actualización de pregunta –

Cuestiones relacionadas