2010-01-16 28 views
7

El C# estados de especificaciones en la sección 5.5 que lee y escribe en ciertos tipos (es decir, bool, char, byte, sbyte, short, ushort, uint, int, float, y los tipos de referencia) están garantizados para ser atómica.¿Cómo garantiza C# la atomicidad de las operaciones de lectura/escritura?

Esto ha despertado mi interés. Como puedes hacer eso? Quiero decir, mi humilde experiencia personal solo me mostró bloquear variables o usar barreras si quería que las lecturas y escrituras se vieran atómicas; eso sería un asesino de rendimiento si tuviera que hacerse para cada lectura/escritura. Y sin embargo C# hace algo con un efecto similar.

Quizás otros idiomas (como Java) lo hagan. En serio, no lo sé. Mi pregunta no tiene la intención de ser específica de un idioma, es solo que sé que C# lo hace.

Entiendo que podría tener que lidiar con ciertas instrucciones específicas del procesador, y puede no ser utilizable en C/C++. Sin embargo, aún me gustaría saber cómo funciona.

[EDIT] Para decir la verdad, creía que las lecturas y escrituras podrían ser no atómicas en ciertas condiciones, como que una CPU podría acceder a una ubicación de memoria mientras otra CPU escribe allí. ¿Esto solo ocurre cuando la CPU no puede tratar todo el objeto a la vez, como porque es demasiado grande o porque la memoria no está alineada en el límite adecuado?

Respuesta

13

La razón por la que estos tipos tienen atomicidad garantizada es porque son todos de 32 bits o menos. Como .NET solo se ejecuta en sistemas operativos de 32 y 64 bits, la arquitectura del procesador puede leer y escribir todo el valor en una sola operación. Esto contrasta, por ejemplo, con un Int64 en una plataforma de 32 bits que debe leerse y escribirse utilizando dos operaciones de 32 bits.

No soy realmente un tipo de hardware, así que me disculpo si mi terminología me hace sonar como un bufón, pero es la idea básica.

+1

Para mí, en realidad suena claro y directo. – Dykam

+0

Meh. De alguna manera pensé que era posible que otra secuencia de comandos accediera a una ubicación de memoria durante otra operación de escritura de la CPU, lo que ocasionaba resultados inconsistentes. ¿Estuve equivocado todo ese tiempo? – zneak

+1

Hay muchos otros problemas aparte de lectura/escritura atómica. Los 2 hilos que operan con las mismas variables pueden conducir a resultados no deseados porque a menudo leen/modifican/escriben y no son atómicos. También hay problemas de visibilidad de memoria en máquinas multiprocesador que necesitan cuidados especiales – nos

-4

No puede. Incluso yendo hasta el lenguaje ensamblador, tienes que usar códigos de operación LOCK especiales para garantizar que otro proceso central o incluso no vaya a desaparecer y acabar con todo tu trabajo duro.

+0

Entonces, ¿eso es lo que hace .net jitter entre bastidores? ¿Agregar bloqueos a casi todas las variables no locales de lectura o escritura? – zneak

+0

No afirmaré que conozco todos los detalles. Estoy hablando desde un punto de vista general. –

3

En x86, las lecturas y escrituras son atómicas de todos modos. Es compatible con el nivel de hardware. Sin embargo, esto no significa que las operaciones como la suma y la multiplicación sean atómicas; requieren una carga, cálculo y luego almacenamiento, lo que significa que pueden interferir. Ahí es donde entra el prefijo de bloqueo.

Ha mencionado las barreras de bloqueo y memoria; no tienen nada que ver con lecturas y escrituras atómicas. No hay forma de que x86 con o sin el uso de barreras de memoria vea un valor medio escrito de 32 bits.

+1

Sí, sé que las multiplicaciones y sus amigos no son atómicos. Nunca ha habido confusión sobre esto de mi parte. De alguna manera creía que una CPU podía acceder a la memoria mientras otra escribía allí, lo que podría generar resultados inconsistentes en la lectura. ¿Puede suceder, y si es así puedo hacer algo para que no suceda? – zneak

+0

No, no puede suceder. Ya lo dije explícitamente. – wj32

4

Es bastante económico implementar la garantía de atomicidad en núcleos x86 y x64 ya que la CLR solo promete atomicidad para variables de 32 bits o menores. Todo lo que se requiere es que la variable esté alineada correctamente y no se ubique en una línea de caché. El compilador JIT lo asegura asignando variables locales en un desplazamiento de apilamiento alineado de 4 bytes. El administrador de montón de GC hace lo mismo para las asignaciones de montón.

Es notable que la garantía de CLR no es muy buena. La promesa de alineación no es lo suficientemente buena como para escribir código que sea consistente para las matrices de dobles. Muy bien demostrado en this thread. La interoperabilidad con código de máquina que usa instrucciones SIMD también es muy difícil por este motivo.

+0

Me resulta curioso que el asignador de memoria intente resolver el problema de alineación para un conjunto de dobles forzándolos al LOH, en lugar de, por ejemplo, a un LOH. diciendo que siempre que el GC asigna o copia un 'doble []' de 32 elementos o más, y el siguiente espacio disponible no está alineado con 64 bits, primero debe asignar y descartar un objeto ficticio de 12 bytes. El 5% de desperdicio de memoria debe ser menos costoso que el costo de forzar cosas al LOH. – supercat

2

Sí, C# y Java garantizan que las cargas y las reservas de algunos tipos primitivos son atómicas, como usted dice. Esto es barato porque los procesadores capaces de ejecutar .NET o JVM sí garantizan que las cargas y las existencias de tipos primitivos adecuadamente alineados son atómicas.

Ahora, lo que ni C ni Java ni los procesadores ejecutados garantizan, y que es caro, está emitiendo barreras de memoria para que esas variables se puedan usar para la sincronización en un programa multiproceso. Sin embargo, en Java y C# puede marcar su variable con el atributo "volátil", en cuyo caso el compilador se encarga de emitir las barreras de memoria apropiadas.

Cuestiones relacionadas