Pregunta

Supongamos que tengo una variable "contador" y hay varios subprocesos que acceden y configuran el valor de "contador" mediante Interlocked, es decir:

int value = Interlocked.Increment(ref counter);

y

int value = Interlocked.Decrement(ref counter);

¿Puedo asumir que el cambio realizado por Interlocked será visible en todos los hilos?

Si no, ¿qué debo hacer para que todos los subprocesos sincronicen la variable?

EDITAR:alguien me sugirió usar volátil.Pero cuando configuro el "contador" como volátil, aparece una advertencia del compilador "la referencia al campo volátil no se tratará como volátil".

Cuando leí la ayuda en línea, decía: "Normalmente, un campo volátil no debe pasarse mediante un parámetro de referencia o de salida".

¿Fue útil?

Solución

InterlockedIncrement/Decrement en CPU x86 (bloqueo add/dec) se crean automáticamente barrera de la memoria lo que brinda visibilidad a todos los subprocesos (es decir, todos los subprocesos pueden ver su actualización en orden, como coherencia de memoria secuencial).La barrera de la memoria hace que se completen todas las cargas/almacenamiento de memoria pendiente. volatile no está relacionado con esta pregunta, aunque C# y Java (y algunos compiladores de C/C++) imponen volatile para hacer la barrera de la memoria.Pero la operación entrelazada ya tiene una barrera de memoria por parte de la CPU.

Por favor también echa un vistazo mi otra respuesta en desbordamiento de pila.

Tenga en cuenta que asumo que InterlockedIncrement/Decrement de C# es una asignación intrínseca al bloqueo add/dec de x86.

Otros consejos

  

¿Puedo suponer que el cambio realizado por Interlocked será visible en todas las discusiones?

Esto depende de cómo se lee el valor. Si "sólo" lee, entonces no, esto no siempre será visible en otros hilos a menos que marque como volátil. Eso hace una advertencia molesto, aunque.

Como alternativa (y mucho OMI preferido), lo leyó usando otra instrucción Interlocked. Esto siempre verá el valor actualizado de todas las discusiones:

int readvalue = Interlocked.CompareExchange(ref counter, 0, 0);

que devuelve el valor de lectura, y si era 0 permutas de TI con 0.

La motivación: la advertencia insinúa que algo no está bien; la combinación de las dos técnicas (volátil y con enclavamiento) no era la forma pretendida para hacer esto.

Actualización: parece que otro enfoque para fiable de 32 bits lee sin utilizar "volátil" es mediante el uso de Thread.VolatileRead como se sugiere en esta respuesta. También hay alguna evidencia de que estoy completamente equivocado sobre el uso de Interlocked de 32 bits lee, por ejemplo, Conectar este tema , aunque me pregunto si la distinción es un poco pedante en la naturaleza.

Lo que realmente quiero decir es: no utilice esta respuesta como su única fuente; Tengo mis dudas sobre esto.

En realidad, no lo son. Si desea modificar de forma segura counter, entonces usted está haciendo lo correcto. Pero si quieres leer directamente volatile tiene que declarar como Interlocked. De lo contrario, el compilador no tiene ninguna razón para creer que <=> va a cambiar porque los <=> operaciones están en código que puede ser que no vea.

Interlocked asegura que sólo 1 hilo a la vez puede actualizar el valor. Para asegurarse de que otros hilos pueden leer el valor correcto (y no un valor almacenado en caché) marcarla como volátil.

int contador público volátil;

No; un Interlocked-en-de sólo escritura por sí sola no asegurarse de que la variable se lee en el código son realmente fresca; un programa que no lee correctamente desde un campo, así podría no ser seguro para subprocesos , incluso en un "modelo de memoria fuerte". Esto se aplica a cualquier forma de asignar a un campo compartido entre hilos.

Este es un ejemplo de código que nunca se dará por terminado debido a la JIT . (Fue modificada a partir barreras de memoria en. NET a ser un programa ejecutable LINQPad actualizado para la pregunta).

// Run this as a LINQPad program in "Release Mode".
// ~ It will never terminate on .NET 4.5.2 / x64. ~
// The program will terminate in "Debug Mode" and may terminate
// in other CLR runtimes and architecture targets.
class X {
    // Adding {volatile} would 'fix the problem', as it prevents the JIT
    // optimization that results in the non-terminating code.
    public int terminate = 0;
    public int y;

    public void Run() {
        var r = new ManualResetEvent(false);
        var t = new Thread(() => {
            int x = 0;
            r.Set();
            // Using Volatile.Read or otherwise establishing
            // an Acquire Barrier would disable the 'bad' optimization.
            while(terminate == 0){x = x * 2;}
            y = x;
        });

        t.Start();
        r.WaitOne();
        Interlocked.Increment(ref terminate);
        t.Join();
        Console.WriteLine("Done: " + y);
    }
}

void Main()
{
    new X().Run();
}

La explicación de barreras de memoria en .NET :

  

Esta vez es JIT, no el hardware. Está claro que JIT ha almacenado en caché el valor de la variable por terminado [en el registro EAX y el] programa está ahora atrapado en el bucle destacó anteriormente. .

     

ya sea utilizando un lock o la adición de un Thread.MemoryBarrier dentro del bucle mientras se solucionará el problema. O incluso se puede utilizar Volatile.Read [o un campo volatile]. El propósito de la barrera de memoria aquí es sólo para suprimir optimizaciones JIT. Ahora que hemos visto como software y hardware puede cambiar el orden de las operaciones de memoria , es el momento para discutir las barreras de memoria ..

Es decir, un adicional de Barrera se requiere construcción en el lado de lectura a evitar problemas con la compilación y JIT reordenamiento / optimizaciones este es un tema diferente a la coherencia de memoria!

Adición <=> aquí habría evitar la optimización JIT, y por lo tanto 'solucionar el problema', incluso si dichos resultados en una advertencia. Este programa también puede ser corregida mediante el uso de <=> o una de las otras diversas operaciones que causan una barrera:. Estas barreras son tanto una parte de la corrección del programa CLR / JIT como las vallas de memoria hardware subyacentes

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top