tipo long int16 c# .net thread-safety int64

long - Bajo C#es peligroso el uso de Int64 en un procesador de 32 bits



interlocked c# (6)

¿Es esto realmente cierto? Sí, como resulta. Si sus registros solo tienen 32 bits, y necesita almacenar un valor de 64 bits en alguna ubicación de memoria, tomará dos operaciones de carga y dos operaciones de almacenamiento. Si su proceso se interrumpe por otro proceso entre estas dos cargas / tiendas, ¡el otro proceso podría corromper la mitad de sus datos! Extraño pero cierto. Esto ha sido un problema en todos los procesadores que se hayan construido: si su tipo de datos es más largo que sus registros, tendrá problemas de concurrencia.

¿Es esto algo de lo que me preocuparía en el mundo real? Si y no. Dado que casi toda la programación moderna tiene su propio espacio de direcciones, solo tendrá que preocuparse por esto si está realizando una programación de subprocesos múltiples.

Si mi aplicación es multiproceso, ¿realmente necesito rodear todas mis asignaciones Int64 con código de bloqueo? Lamentablemente, sí, si quieres ser técnico. Por lo general, en la práctica es más fácil usar un Mutex o un Semáforo alrededor de bloques de código más grandes que bloquear cada instrucción de conjunto individual en variables accesibles globalmente.

Leí en la documentación de MS que la asignación de un valor de 64 bits en una computadora Intel de 32 bits no es una operación atómica; es decir, la operación no es segura para subprocesos. Esto significa que si dos personas asignan simultáneamente un valor a un campo Int64 estático, el valor final del campo no se puede predecir.

Pregunta de tres partes:

  • ¿Es esto realmente cierto?
  • ¿Es esto algo de lo que me preocuparía en el mundo real?
  • Si mi aplicación es multiproceso, ¿realmente necesito rodear todas mis asignaciones Int64 con código de bloqueo?

En una plataforma x86 de 32 bits, el tamaño más grande de memoria atómica es de 32 bits.

Esto significa que si algo escribe o lee desde una variable de tamaño de 64 bits, es posible que esa lectura / escritura se anule durante la ejecución.

  • Por ejemplo, empiezas a asignar un valor a una variable de 64 bits.
  • Después de que se escriben los primeros 32 bits, el sistema operativo decide que otro proceso obtendrá tiempo de CPU.
  • El siguiente proceso intenta leer la variable que estaba asignando.

Esa es solo una posible condición de carrera con una asignación de 64 bits en una plataforma de 32 bits.

Sin embargo, incluso con la variable de 32 bits puede haber condiciones de carrera con la lectura y la escritura, por lo que cualquier variable compartida debe sincronizarse de alguna manera para resolver estas condiciones de carrera.


Esto no es sobre cada variable que encuentras. Si alguna variable se usa como un estado compartido o algo (incluidos, entre otros, algunos campos static ), debe solucionar este problema. Es completamente ajeno a las variables locales que no se elevan como consecuencia de estar encerradas en un cierre o una transformación de iterador y son usadas por una sola función (y, por lo tanto, un solo hilo) a la vez.


Incluso si las escrituras fueran atómicas, es probable que aún necesite un bloqueo cada vez que acceda a la variable. Si no hiciste eso, al menos deberías hacer que la variable sea volatile para asegurarte de que todos los hilos vieron el nuevo valor la próxima vez que lean la variable (que es casi siempre lo que quieres). Eso te permite hacer conjuntos atómicos y volátiles, pero tan pronto como quieras hacer algo más interesante, como agregarle 5, volverás a bloquear.

Bloquear la programación libre es muy, muy difícil de hacer bien. Debe saber exactamente lo que está haciendo y mantener la complejidad en un código tan pequeño como sea posible. Personalmente, rara vez trato de intentarlo con excepción de patrones muy conocidos, como el uso de un inicializador estático para inicializar una colección y luego leer de la colección sin bloquear.

El uso de la clase Interlocked puede ayudar en algunas situaciones, pero casi siempre es mucho más fácil simplemente sacar un bloqueo. Los bloqueos no controlados son "bastante baratos" (hay que reconocer que se vuelven caros con más núcleos, pero también lo hace todo). No pierdas el tiempo con el código sin bloqueo hasta que tengas una buena evidencia de que realmente va a hacer una gran diferencia.


Si tiene una variable compartida (por ejemplo, como un campo estático de una clase, o como un campo de un objeto compartido), y ese campo u objeto se va a usar entre hilos, entonces, sí, debe asegurarse ese acceso a esa variable está protegido a través de una operación atómica. El procesador x86 tiene elementos intrínsecos para asegurarse de que esto suceda, y esta instalación está expuesta a través de los métodos de clase System.Threading.Interlocked.

Por ejemplo:

class Program { public static Int64 UnsafeSharedData; public static Int64 SafeSharedData; static void Main(string[] args) { Action<Int32> unsafeAdd = i => { UnsafeSharedData += i; }; Action<Int32> unsafeSubtract = i => { UnsafeSharedData -= i; }; Action<Int32> safeAdd = i => Interlocked.Add(ref SafeSharedData, i); Action<Int32> safeSubtract = i => Interlocked.Add(ref SafeSharedData, -i); WaitHandle[] waitHandles = new[] { new ManualResetEvent(false), new ManualResetEvent(false), new ManualResetEvent(false), new ManualResetEvent(false)}; Action<Action<Int32>, Object> compute = (a, e) => { for (Int32 i = 1; i <= 1000000; i++) { a(i); Thread.Sleep(0); } ((ManualResetEvent) e).Set(); }; ThreadPool.QueueUserWorkItem(o => compute(unsafeAdd, o), waitHandles[0]); ThreadPool.QueueUserWorkItem(o => compute(unsafeSubtract, o), waitHandles[1]); ThreadPool.QueueUserWorkItem(o => compute(safeAdd, o), waitHandles[2]); ThreadPool.QueueUserWorkItem(o => compute(safeSubtract, o), waitHandles[3]); WaitHandle.WaitAll(waitHandles); Debug.WriteLine("Unsafe: " + UnsafeSharedData); Debug.WriteLine("Safe: " + SafeSharedData); } }

Los resultados:

Inseguro : -24050275641 Seguro : 0

En una nota al margen interesante, ejecuté esto en modo x64 en Vista 64. Esto muestra que los campos de 64 bits se tratan como campos de 32 bits por el tiempo de ejecución, es decir, las operaciones de 64 bits no son atómicas. ¿Alguien sabe si esto es un problema de CLR o un problema de x64?


MSDN :

Asignar una instancia de este tipo no es seguro para subprocesos en todas las plataformas de hardware porque la representación binaria de esa instancia puede ser demasiado grande para asignar en una sola operación atómica.

Pero también:

Al igual que con cualquier otro tipo, la lectura y la escritura en una variable compartida que contiene una instancia de este tipo debe estar protegida por un bloqueo para garantizar la seguridad de subprocesos.