2009-07-07 27 views
11

Hoy me encontré con esta pregunta:¿Una instrucción de ensamblador siempre se ejecuta atómicamente?

tiene un código de

static int counter = 0; 
void worker() { 
    for (int i = 1; i <= 10; i++) 
     counter++; 
} 

Si worker se llamaría a partir de dos hilos diferentes, ¿qué valor se counter tener después ambos están acabados?

Sé que en realidad podría ser cualquier cosa. Pero mis tripas internas me dice, que counter++ más probable es que se traducirán en una sola instrucción ensamblador, y si ambos hilos son ejecutar en el mismo núcleo, counter habrá 20.

Pero ¿y si esos hilos se ejecutan en diferentes núcleos o procesadores, ¿podría haber una condición de carrera en su microcódigo? ¿Una instrucción de ensamblador siempre se puede ver como una operación atómica?

Respuesta

17

Específicamente para x86, y en relación con su ejemplo: counter++, hay una serie de formas en que se podría compilar. El ejemplo más trivial es:

inc counter 

Esto se traduce en las siguientes operaciones de micro:

  • carga counter a un registro oculto en la CPU
  • incremento del registro
  • tienda del registro actualizado en counter

Esto es essentia LLY lo mismo que:

mov eax, counter 
inc eax 
mov counter, eax 

Tenga en cuenta que si algún otro agente actualiza counter entre la carga y la tienda, que no se refleja en counter después de la tienda.Este agente podría ser otro hilo en el mismo núcleo, otro núcleo en la misma CPU, otra CPU en el mismo sistema o incluso algún agente externo que utiliza DMA (acceso directo a la memoria).

Si usted quiere garantizar que este inc es atómica, utilice el prefijo lock:

lock inc counter 

lock garantiza que nadie puede actualizar counter entre la carga y la tienda.


cuanto a las instrucciones más complicadas, por lo general, no se puede asumir que van a ejecutar atómicamente, si no apoyan el prefijo lock.

+1

+1: ¡una buena inmersión en las especificaciones de ensamblaje x86! – Juergen

-3

Creo que obtendrás una condición de carrera en el acceso.

Si quería asegurarse una operación atómica en el incremento del contador, entonces necesitaría usar el contador ++.

+2

realmente no importa con los compiladores modernos, es la misma operación. – vava

3

No, no puede suponer esto. A menos que se indique claramente en la especificación del compilador. Y, además, nadie puede garantizar que una única instrucción de ensamblador sea atómica. En la práctica, cada instrucción de ensamblador se traduce al número de operaciones de microcódigo - uops.
También el problema de la condición de carrera está estrechamente relacionado con el modelo de memoria (coherencia, secuencia, coherencia de lanzamiento, etc.), para cada uno la respuesta y el resultado podrían ser diferentes.

6

No siempre - en algunas arquitecturas, una instrucción de ensamblaje se traduce en una instrucción de código de máquina, mientras que en otras no.

Además - se puede Nunca suponer que el idioma del programa que está utilizando está compilando una línea aparentemente simple de código en una instrucción de montaje. Además, en algunas arquitecturas, no se puede suponer que un código de máquina se ejecutará atómicamente.

Use técnicas de sincronización adecuada en lugar, dependiendo de la lengua que está codificando en.

+0

explique el -1 por favor –

+0

¿Por qué el voto a favor? –

+3

Una instrucción de ensamblaje se puede traducir a muchas instrucciones de microcódigo –

5
  1. operaciones de incremento/decremento sobre las variables de 32 bits o menos enteros en un único procesador 32 bits sin Hyper-Threading son atómicos.
  2. En un procesador con tecnología Hyper-Threading o en un sistema multiprocesador, NO se garantiza que las operaciones de incremento/decremento se ejecuten de forma atómica.
+0

este bit lo sé, me preguntaba, ¿qué podría pasar? ¿Microcode entraría en condiciones de carrera? – vava

+0

Sí, en el caso 2.) tiene una condición de carrera y no se garantiza que el resultado sea siempre 20. –

+3

Esto es incorrecto.Incluso en un único sistema de CPU de un solo hilo aún puede tener agentes externos actualizando la memoria a través de DMA o PCI. Incluso una simple instrucción _inc memory_ no debe asumirse como atómica. Todavía tiene que usar _lock inc memory_ –

2

Otra cuestión es que si no se declara la variable como volátil, el código generado probablemente no actualizar la memoria en cada repetición del bucle, sólo al final del bucle de la memoria se actualiza.

+0

muy buen punto, no he pensado en esto – vava

4

Invalidado por el comentario de Nathan: Si recuerdo correctamente mi ensamblador Intel x86, la instrucción INC solo funciona para registros y no funciona directamente para ubicaciones de memoria.

Así que un contador ++ no sería una instrucción única en el ensamblador (simplemente ignorando la parte de incremento posterior).Sería al menos tres instrucciones: cargar la variable del contador para registrar, incrementar el registro, cargar el registro de nuevo en el contador. Y eso es solo para la arquitectura x86.

En resumen, no confíe en que sea atómico a menos que esté especificado por las especificaciones del lenguaje y que el compilador que está utilizando cumpla con las especificaciones.

+1

'inc m32' es válido en x86 –

+1

Gracias por la corrección Nathan. – jop

6

La respuesta es: ¡depende!

Aquí es un poco de confusión en torno, lo que es una instrucción ensamblador. Normalmente, una instrucción de ensamblador se traduce en exactamente una instrucción de máquina. La exoneración se produce cuando utiliza macros, pero debe tenerlo en cuenta.

Dicho esto, la cuestión se reduce la instrucción es una máquina atómica?

En los viejos tiempos, lo era. Pero hoy, con CPUs complejas, instrucciones de larga duración, hyperthreading, ... no lo es. Algunas CPU garantizan que algunas instrucciones de incremento/decremento son atómicas. La razón es que están limpios para una sincronización simple.

también algunos comandos de la CPU no son tan problemáticos. Cuando tiene una búsqueda simple (de una pieza de datos que el procesador puede obtener de una sola pieza), la búsqueda en sí misma es, por supuesto, atómica, porque no hay nada que dividir en absoluto. Pero cuando tienes datos no alineados, se vuelven complicados nuevamente.

La respuesta es: Depende. Lea atentamente el manual de instrucciones de la máquina del vendedor. En duda, no lo es!

Editar: Oh, lo vi ahora, también se pide ++ contador. La frase "con la mayor probabilidad de ser traducida" no se puede confiar en absoluto. ¡Esto también depende en gran parte del compilador, por supuesto! Se vuelve más difícil cuando el compilador realiza diferentes optimizaciones.

+2

He votado por "Algunas CPU garantizan que algunas instrucciones de incremento/disminución son atómicas", con mi énfasis en * algunos *. Además, tenga en cuenta que incluso una búsqueda simple no es necesariamente atómica. Por ejemplo, si cruza un límite de caché, puede hacerse en dos partes. Incluso para datos alineados si el tipo de datos es más grande que la línea de caché. –

+0

Hola Nathan, ¡gracias por el comentario! No pensé en las cachelines. Pero tienes razón, esto también puede ser una complicación adicional. Yo diría que una caché es (en su mayoría) un múltiplo del tamaño de palabra normal (32/64 bit), ¿no? Esa es la razón por la que agregué "que el procesador puede ir de una pieza"). – Juergen

1

Puede que no sea una respuesta real a su pregunta, pero (suponiendo que esto es C#, .NET o en otro idioma) si desea counter++ a ser realmente multi-hilo atómica, podría utilizar System.Threading.Interlocked.Increment(counter).

Ver otras respuestas para información real sobre las muchas maneras por las cuales/counter++ cómo no podía ser atómica. ;-)

-1

En muchos otros procesadores, la separación entre el sistema de memoria y el procesador es mayor. (a menudo estos procesadores pueden ser pequeños o grandes dependiendo del sistema de memoria, como ARM y PowerPC), esto también tiene consecuencias para el comportamiento atómico si el sistema de memoria puede reordenar las lecturas y escrituras.

Para este propósito, existen barreras de memoria (http://en.wikipedia.org/wiki/Memory_barrier)

Así que en resumen, mientras que las instrucciones atómicas son suficientes por Intel (con los prefijos de bloqueo pertinentes), se debe hacer más en la no-Intel, ya que la memoria I/O podría no estar en el mismo orden.

Este es un problema conocido cuando se transfieren soluciones "sin bloqueo" de Intel a otras arquitecturas.

(Tenga en cuenta que varios procesadores (no multinúcleo) sistemas de x86 también parecen necesitar barreras de memoria, por lo menos en el modo de 64 bits.

+0

En cuanto a su edición, ¿por qué se limita solo al modo de 64 bits? ¿Por qué te limitas a sistemas multiprocesador? De hecho, no solo no puede suponer ningún pedido en sistemas mutli-core, sino que tampoco puede hacerlo en sistemas de un solo núcleo y múltiples hilos. –

+0

Afaik en sistemas multinúcleo (pero no multiprocesador) la caché común y la coherencia del caché causa un sistema de memoria síncrona. El IIRC de 64 bits se debió al hecho de que cuando una carga de 64 bits de memoria cruzaba una línea de caché, tardaba el doble del ciclo de memoria en recuperarse. Pero lea los manuales Intel o AMD ABI/processor (busque sfence) y compruébelo usted mismo. Solo intento regurgitar lo que recuerdo sin renovar la investigación. –

0

En la mayoría de los casos, sin. De hecho, en x86, se puede llevar a cabo la instrucción

push [address] 

que en C, sería algo así como:

*stack-- = *address; 

Esto realiza t wo transferencias de memoria en una instrucción.

Eso es básicamente imposible de hacer en 1 ciclo de reloj, no menos porque uno ¡la transferencia de memoria tampoco es posible en un ciclo!

Cuestiones relacionadas