¿Cómo leer correctamente un campo int Interlocked.Increment?

Supongamos que tengo un campo int no volátil y un hilo que es Interlocked.Increment s it. ¿Puede otro hilo leer esto de forma segura, o la lectura también debe estar enclavada?

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 lograr eso.

Sin embargo, me he topado con esta respuesta que sugiere que en realidad las lecturas simples siempre verán la versión actual de un valor ed. De Interlocked.Increment Intenso, 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 leer de forma segura el valor más actual de este campo int sin Interlocked ?

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

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

Hay varios participantes que pueden alterar el código: comstackdor, comstackdor JIT y CPU. Realmente no importa cuál de ellos muestra que su 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 en caché, pero la segunda obtendría un valor actualizado porque la barrera de memoria de la primera lectura volátil ha forzado una actualización de caché si fuera necesario. En la práctica, este detalle tiene poca importancia cuando se escribe el código.

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

Considere el caso de que está comenzando con un campo int , que hace que sea imposible usar Interlocked.Read() , por lo que leerá el valor directamente en su lugar, ya que de todos modos es atómico. Sin embargo, más tarde en su desarrollo, usted u otra persona decide que se necesita long : el comstackdor no lo advertirá, pero ahora puede tener un error sutil: ya no se garantiza que el acceso de lectura sea atómico. Encontré usar Interlocked.CompareExchange() la mejor alternativa aquí; Puede ser más lento dependiendo de las instrucciones subyacentes del procesador, pero es más seguro a largo plazo. No sé lo suficiente sobre las Thread.VolatileRead() internas de Thread.VolatileRead() aunque; 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 ciclo o de cualquier método estricto, ya que incluso si las escrituras son volátiles y / o tienen una barrera de memoria, nada le dice al comstackdor que el valor del campo realmente puede cambiar entre dos lecturas . Por lo tanto, el campo debe ser volatile o cualquiera de las construcciones dadas debe ser utilizado.

Mis dos centavos.

Tiene razón en que no necesita una instrucción especial para leer atómicamente un entero de 32 bits, sin embargo, lo que eso significa es que obtendrá el valor “total” (es decir, no obtendrá 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 donde debe decidir si necesita usar algún otro método de sincronización para controlar el acceso, por ejemplo, si está usando este valor para leer un miembro de una matriz, etc.


En pocas palabras, la atomicidad garantiza que la operación se realice de manera completa e indivisible. Dada alguna operación A que contenía N pasos, si llegaste a la operación inmediatamente después de A , puedes estar seguro de que todos los N pasos sucedieron aislados de las operaciones simultáneas.

Si tiene dos hilos que ejecutaron la operación atómica A , se garantiza que verá solo el resultado completo de uno de los dos hilos. Si desea coordinar los hilos, las operaciones atómicas podrían usarse para crear la sincronización requerida. Pero las operaciones atómicas en sí mismas no proporcionan una sincronización de mayor nivel. La familia de métodos Enclavados está disponible para proporcionar algunas operaciones atómicas fundamentales.

La sincronización es un tipo más amplio de control de concurrencia, a menudo basado en 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 descargadas y tenga una vista consistente de la memoria. Las lecturas volátiles son una forma de garantizar un acceso uniforme a una ubicación de memoria determinada.

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

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