threads thread safe lock concurrentbag concurrent c# .net multithreading thread-safety

c# - thread - ¿Cómo leer correctamente un campo int Interlocked.Increment''ed?



thread safe queue c# (4)

Sí, todo lo que has leído es correcto. Interlocked.Increment está diseñado para que las lecturas normales no sean falsas al realizar los cambios en el campo. Leer un campo no es peligroso, escribir un campo es.

Supongamos que tengo un campo int no volátil y un subproceso que está Interlocked.Increment . ¿Puede otro hilo leer con seguridad esto directamente, o la lectura también necesita estar entrelazada?

Anteriormente pensé que tenía que usar una lectura entrelazada para garantizar que estoy viendo el valor actual, ya que, después de todo, el campo no es volátil. He estado usando Interlocked.CompareExchange(int, 0, 0) para lograrlo.

Sin embargo, me he topado con esta respuesta que sugiere que las lecturas simples siempre verán la versión actual de un valor Interlocked.Increment Incrementado, y dado que la lectura int ya es atómica, no hay necesidad de hacer nada especial. También encontré una solicitud en la que Microsoft rechaza una solicitud de Interlocked.Read (ref int) , sugiriendo además que esto es completamente redundante.

Entonces, ¿puedo realmente leer con seguridad el valor más actual de dicho campo int sin Interlocked ?


Si desea garantizar que el otro subproceso leerá el último valor, debe usar Thread.VolatileRead() . (*)

La operación de lectura en sí misma es atómica, por lo que no causará ningún problema, pero sin la lectura volátil puede obtener un valor antiguo del caché o el compilador puede optimizar su código y eliminar la operación de lectura por completo. Desde el punto de vista del compilador, es suficiente que el código funcione en un entorno de un solo hilo. Las operaciones volátiles y las barreras de memoria se utilizan para limitar la capacidad del compilador para optimizar y reordenar el código.

Hay varios participantes que pueden alterar el código: compilador, compilador JIT y CPU. Realmente no importa cuál de ellos muestre que tu código está roto. Lo único importante es el modelo de memoria .NET, ya que especifica las reglas que deben cumplir todos los participantes.

(*) Thread.VolatileRead() realmente no obtiene el último valor. Leerá el valor y agregará una barrera de memoria después de la lectura. La primera lectura volátil puede obtener un valor almacenado en la memoria caché, pero la segunda recibirá un valor actualizado porque la barrera de memoria de la primera lectura volátil ha forzado una actualización de la memoria caché si fuera necesario. En la práctica este detalle tiene poca importancia a la hora de escribir el código.


Tienes razón en que no necesitas una instrucción especial para leer atómicamente un entero de 32 bits, sin embargo, lo que significa es que obtendrás el valor "completo" (es decir, no recibirás parte de una escritura y parte de otra ). No tiene garantías de que el valor no haya cambiado una vez que lo haya leído.

Es en este punto en el que debe decidir si necesita usar algún otro método de sincronización para controlar el acceso; por ejemplo, si está utilizando este valor para leer un miembro de una matriz, etc.

En pocas palabras, la atomicidad garantiza que una operación se realice de forma completa e indivisible. Dada una operación A que contenía N pasos, si llegó a la operación inmediatamente después de A , puede estar seguro de que todos los N pasos sucedieron aislados de las operaciones concurrentes.

Si tenía dos subprocesos que ejecutaron la operación atómica A , tiene la garantía de que solo verá el resultado completo de uno de los dos subprocesos. Si desea coordinar los hilos, se pueden utilizar operaciones atómicas para crear la sincronización requerida. Pero las operaciones atómicas en sí mismas no proporcionan una sincronización de nivel superior. La familia de métodos entrelazados está disponible para proporcionar algunas operaciones atómicas fundamentales.

La sincronización es un tipo más amplio de control de concurrencia, a menudo construido alrededor de operaciones atómicas . La mayoría de los procesadores incluyen barreras de memoria que le permiten asegurarse de que todas las líneas de caché estén vacías y que tenga una vista coherente de la memoria. Las lecturas volátiles son una forma de garantizar un acceso consistente a una ubicación de memoria determinada.

Si bien no es inmediatamente aplicable a su problema, leer sobre ACID (atomicidad, consistencia, aislamiento y durabilidad) con respecto a las bases de datos puede ayudarlo con la terminología.


Un poco de un problema meta, pero un buen aspecto sobre el uso de Interlocked.CompareExchange(ref value, 0, 0) (ignorando la desventaja obvia que es más difícil de entender cuando se usa para leer) es que funciona independientemente de int o de long . Es cierto que las lecturas int son siempre atómicas, pero long lecturas long no lo son o pueden no serlo, dependiendo de la arquitectura. Desafortunadamente, Interlocked.Read(ref value) solo funciona si el value es de tipo long .

Considere el caso de que esté comenzando con un campo int , lo que hace que sea imposible utilizar Interlocked.Read() , por lo que leerá el valor directamente, ya que de todos modos es atómico. Sin embargo, más adelante en el desarrollo, usted o alguien más decide que se requiere long ; el compilador no le advertirá, pero ahora puede tener un error sutil: ya no se garantiza que el acceso de lectura sea atómico. Encontré que usar Interlocked.CompareExchange() la mejor alternativa aquí; Puede ser más lento dependiendo de las instrucciones del procesador subyacente, pero a la larga es más seguro. No sé lo suficiente acerca de los Thread.VolatileRead() internos de Thread.VolatileRead() ; Podría ser "mejor" con respecto a este caso de uso, ya que proporciona aún más firmas.

Sin embargo, no trataría de leer el valor directamente (es decir, sin ninguno de los mecanismos anteriores) dentro de un bucle o de cualquier método estricto, ya que incluso si las escrituras son volátiles y / o con la memoria bloqueada, nada le dice al compilador que El valor del campo puede realmente cambiar entre dos lecturas . Por lo tanto, el campo debe ser volatile o se debe usar cualquiera de las construcciones dadas.

Mis dos centavos.