Pregunta Volátil vs. Enclavado vs. bloqueo


Digamos que una clase tiene un public int counter campo al que se accede por múltiples hilos. Esta int solo se incrementa o disminuye.

Para incrementar este campo, ¿qué enfoque debería usarse y por qué?

  • lock(this.locker) this.counter++;,
  • Interlocked.Increment(ref this.counter);,
  • Cambiar el modificador de acceso de counter a public volatile.

Ahora que he descubierto volatile, He estado eliminando muchos lock declaraciones y el uso de Interlocked. ¿Pero hay una razón para no hacer esto?


586
2017-09-30 19:25


origen


Respuestas:


Peor (no funcionará)

Cambiar el modificador de acceso de counter a public volatile

Como han mencionado otras personas, esto por sí solo no es seguro en absoluto. El punto de volatile es que múltiples hilos que se ejecutan en múltiples CPU pueden almacenar y almacenar datos y reordenar las instrucciones.

Si esto es no  volatile, y la CPU A incrementa un valor, entonces la CPU B puede no ver ese valor incrementado hasta algún tiempo después, lo que puede causar problemas.

Si esto es volatile, esto solo garantiza que las dos CPU vean los mismos datos al mismo tiempo. No les impide en absoluto entrelazar sus lecturas y operaciones de escritura, que es el problema que están tratando de evitar.

Segundo mejor:

lock(this.locker) this.counter++;

Esto es seguro de hacer (siempre que recuerde lock en cualquier otro lugar al que accedas this.counter) Impide que otros subprocesos ejecuten cualquier otro código que esté protegido por locker. El uso de bloqueos también previene los problemas de reordenamiento multi-CPU como se indica arriba, lo cual es genial.

El problema es que el bloqueo es lento y si vuelve a usar el locker en otro lugar que no está realmente relacionado, entonces puedes terminar bloqueando tus otros hilos sin ningún motivo.

Mejor

Interlocked.Increment(ref this.counter);

Esto es seguro, ya que efectivamente lee, incrementa y escribe en 'un golpe' que no puede ser interrumpido. Debido a esto, no afectará a ningún otro código, y tampoco deberá recordar bloquearlo en otro lugar. También es muy rápido (como dice MSDN, en las CPU modernas, esto es, literalmente, una sola instrucción de CPU).

 No estoy del todo seguro, sin embargo, si se trata de otras CPU reordenando cosas, o si también necesita combinar volátiles con el incremento.

InterlockedNotes:

  1. LOS MÉTODOS EN BLOQUEO SON CONCURRENTEMENTE SEGUROS EN CUALQUIER NÚMERO DE CORE O CPU.
  2. Los métodos entrelazados aplican una valla completa alrededor de las instrucciones que ejecutan, por lo que el reordenamiento no ocurre.
  3. Métodos enclavados no necesita o incluso no admite el acceso a un campo volátil, como volátil se coloca una media valla alrededor de las operaciones en el campo dado y se enclava con la valla completa.

Nota a pie de página: para qué es volátil realmente bueno.

Como volatile no previene este tipo de problemas de subprocesamiento múltiple, ¿para qué sirve? Un buen ejemplo es decir que tiene dos hilos, uno que siempre escribe en una variable (por ejemplo, queueLength), y uno que siempre lee desde esa misma variable.

Si queueLength no es volátil, el subproceso A puede escribir cinco veces, pero el subproceso B puede ver esas escrituras como retrasadas (o incluso potencialmente en el orden incorrecto).

Una solución sería bloquear, pero también podría usar volátil en esta situación. Esto aseguraría que el hilo B siempre verá la cosa más actualizada que el hilo A ha escrito. Sin embargo, tenga en cuenta que esta lógica solamente funciona si tienes escritores que nunca leen, y lectores que nunca escriben, y si lo que estás escribiendo es un valor atómico. Tan pronto como realice una única lectura-modificación-escritura, debe ir a las operaciones Interbloqueadas o usar un Bloqueo.


754
2017-09-30 20:13



EDITAR: Como se señaló en los comentarios, estos días estoy feliz de usar Interlocked para los casos de variable única donde esta obviamente bueno. Cuando se vuelve más complicado, todavía voy a volver a bloquear ...

Utilizando volatile no ayudará cuando necesite aumentar, porque la lectura y la escritura son instrucciones separadas. Otro hilo podría cambiar el valor después de haber leído pero antes de que vuelva a escribir.

Personalmente, casi siempre me bloqueo, es más fácil acertar de una manera que es obviamente más que volatilidad o Incrustado. En lo que a mí respecta, el multi-threading sin bloqueo es para expertos en hilos reales, de los cuales no soy uno. Si Joe Duffy y su equipo construyen geniales bibliotecas que paralelizarán las cosas sin tanto bloqueo como algo que yo construiría, eso es fabuloso, y lo usaré en un abrir y cerrar de ojos, pero cuando estoy haciendo el enhebrado yo mismo, intento mantenlo simple.


125
2017-09-30 19:29



"volatile"no reemplaza Interlocked.Increment! Solo se asegura de que la variable no se guarde en caché, sino que se usa directamente.

Incrementar una variable requiere en realidad tres operaciones:

  1. leer
  2. incremento
  3. escribir

Interlocked.Increment realiza las tres partes como una operación atómica única.


41
2017-09-30 19:32



El bloqueo o el incremento entrelazado es lo que está buscando.

Volatilidad definitivamente no es lo que buscas: simplemente le dice al compilador que trate la variable como siempre cambiante, incluso si la ruta del código actual permite al compilador optimizar de otra manera una lectura de la memoria.

p.ej.

while (m_Var)
{ }

si m_Var se establece en falso en otro hilo pero no se declara como volátil, el compilador puede convertirlo en un bucle infinito (pero no siempre lo hará) haciéndolo contra un registro de CPU (por ejemplo, EAX porque eso fue qué m_Var fue captado desde el principio) en lugar de emitir otra lectura a la ubicación de memoria de m_Var (esto puede almacenarse en caché, no lo sabemos y no nos importa y ese es el punto de coherencia de caché de x86 / x64). Todas las publicaciones anteriores de otros que mencionaron el reordenamiento de las instrucciones simplemente muestran que no entienden las arquitecturas x86 / x64. Volátil no emite barreras de lectura / escritura, tal como lo implican las publicaciones anteriores que dicen 'impide el reordenamiento'. De hecho, gracias de nuevo al protocolo MESI, se nos garantiza que el resultado que leemos siempre es el mismo en todas las CPU, independientemente de si los resultados reales se han retirado a la memoria física o simplemente residen en la caché de la CPU local. No entraré demasiado en los detalles de esto, pero tenga la seguridad de que si esto falla, ¡Intel / AMD probablemente emita un retiro del procesador! Esto también significa que no tenemos que preocuparnos por la ejecución fuera de servicio, etc. Los resultados siempre están garantizados para retirarse en orden, de lo contrario estamos llenos.

Con Incremento entrelazado, el procesador necesita salir, obtener el valor de la dirección indicada, luego incrementarlo y volver a escribirlo, todo ello teniendo propiedad exclusiva de toda la línea de caché (lock xadd) para asegurarse de que ningún otro procesador pueda modificar es valioso.

Con la volatilidad, igual terminará con solo 1 instrucción (suponiendo que el JIT es eficiente como debería) - inc dword ptr [m_Var]. Sin embargo, el procesador (cpuA) no solicita la propiedad exclusiva de la línea de caché mientras hace todo lo que hizo con la versión interconectada. Como se puede imaginar, esto significa que otros procesadores podrían escribir un valor actualizado en m_Var una vez que lo haya leído cpuA. Entonces, en vez de haber incrementado el valor dos veces, terminas con solo una vez.

Espero que esto aclare el problema.

Para obtener más información, consulte "Comprender el impacto de las técnicas de bloqueo bajo en aplicaciones multiproceso" - http://msdn.microsoft.com/en-au/magazine/cc163715.aspx

PD. ¿Qué provocó esta respuesta tan tardía? Todas las respuestas fueron tan descaradamente incorrectas (especialmente la marcada como respuesta) en su explicación que simplemente tuve que aclararlo para cualquier otra persona que lea esto. encoge de hombros

p.p.s. Supongo que el objetivo es x86 / x64 y no IA64 (tiene un modelo de memoria diferente). Tenga en cuenta que las especificaciones de ECMA de Microsoft están arruinadas porque especifica el modelo de memoria más débil en lugar del más fuerte (siempre es mejor especificarlo contra el modelo de memoria más fuerte para que sea coherente en todas las plataformas; de lo contrario, el código se ejecutaría 24/7 en x86 / x64 puede no ejecutarse en absoluto en IA64 aunque Intel ha implementado un modelo de memoria similar fuerte para IA64) - Microsoft admitió esto por sí mismo - http://blogs.msdn.com/b/cbrumme/archive/2003/05/17/51445.aspx.


38
2018-06-23 15:08



Las funciones enclavadas no se bloquean. Son atómicos, lo que significa que pueden completarse sin la posibilidad de un cambio de contexto durante el incremento. Entonces no hay posibilidad de estancamiento o espera.

Yo diría que siempre deberías preferirlo a un candado e incrementarlo.

Volatile es útil si necesita escribir en un hilo para leer en otro y si desea que el optimizador no reordene las operaciones en una variable (porque las cosas están sucediendo en otro hilo que el optimizador desconoce). Es una elección ortogonal de cómo se incrementa.

Este es un artículo realmente bueno si desea leer más sobre el código de bloqueo y la forma correcta de abordarlo.

http://www.ddj.com/hpc-high-performance-computing/210604448


15
2017-09-30 19:27



lock (...) funciona, pero puede bloquear un subproceso, y puede provocar un interbloqueo si otro código está utilizando los mismos bloqueos de una manera incompatible.

Interbloqueado. * Es la forma correcta de hacerlo ... mucho menos sobrecarga, ya que las CPUs modernas lo soportan como primitivo.

volátil por sí mismo no es correcto. Un hilo que intente recuperar y luego escribir de nuevo un valor modificado aún podría entrar en conflicto con otro hilo haciendo lo mismo.


11
2017-09-30 19:32



Apoyo la respuesta de Jon Skeet y quiero agregar los siguientes enlaces para todos los que quieran saber más sobre "volátil" y enclavado:

La atomicidad, la volatilidad y la inmutabilidad son diferentes, primera parte - (Fabulous Adventures In Coding, de Eric Lippert)

La atomicidad, la volatilidad y la inmutabilidad son diferentes, parte dos 

La atomicidad, la volatilidad y la inmutabilidad son diferentes, parte tres

Sayonara Volátil - (Instantánea de Wayback Machine del Weblog de Joe Duffy tal como apareció en 2012) 


8
2018-04-29 22:04



Hice algunas pruebas para ver cómo funciona realmente la teoría: kennethxu.blogspot.com/2009/05/interlocked-vs-monitor-performance.html. Mi prueba estuvo más centrada en CompareExchnage pero el resultado para Increment es similar. Enclavado no es necesario más rápido en el entorno de múltiples CPU. Aquí está el resultado de la prueba para Increment en un servidor de CPU de 2 años de antigüedad. Ten en cuenta que la prueba también implica la lectura segura después del aumento, que es típica en el mundo real.

D:\>InterlockVsMonitor.exe 16
Using 16 threads:
          InterlockAtomic.RunIncrement         (ns):   8355 Average,   8302 Minimal,   8409 Maxmial
    MonitorVolatileAtomic.RunIncrement         (ns):   7077 Average,   6843 Minimal,   7243 Maxmial

D:\>InterlockVsMonitor.exe 4
Using 4 threads:
          InterlockAtomic.RunIncrement         (ns):   4319 Average,   4319 Minimal,   4321 Maxmial
    MonitorVolatileAtomic.RunIncrement         (ns):    933 Average,    802 Minimal,   1018 Maxmial

6
2018-05-24 23:12