concurrency x86 compare-and-swap

concurrency - ¿Es x86 CMPXCHG atómico? De ser así, ¿por qué necesita BLOQUEO?



compare-and-swap (3)

La documentación de Intel dice

Esta instrucción se puede usar con un prefijo LOCK para permitir que la instrucción se ejecute atómicamente.

Mi pregunta es

  1. ¿Puede CMPXCHG operar con dirección de memoria? Del documento parece que no, pero ¿alguien puede confirmar que solo funciona con VALOR real en los registros, no con la dirección de memoria?

  2. Si CMPXCHG no es atómico y se debe implementar un CAS de alto nivel de lenguaje a través de LOCK CMPXCHG (con el prefijo LOCK ), ¿cuál es el propósito de introducir tal instrucción?


El prefijo LOCK es bloquear el acceso a la memoria para el comando actual, de modo que otros comandos que están en la tubería de la CPU puedan acceder a la memoria en este momento. Usando el prefijo LOCK, la ejecución del comando no será interrumpida por otro comando en la tubería de la CPU debido al acceso a la memoria de otros comandos que se ejecutan al mismo tiempo. El manual de INTEL dice:

El prefijo LOCK puede anteponerse solo a lo siguiente en las instrucciones y solo a aquellas formas de las instrucciones donde el operando de destino es un operando de memoria: ADD, ADC, AND, BTC, BTR, BTS, CMPXCHG, CMPXCH8B, CMPXCHG16B, DEC, INC , NEG, NOT, OR, SBB, SUB, XOR, XADD y XCHG. Si el prefijo LOCK se usa con una de estas instrucciones y el operando de origen es un operando de memoria, se puede generar una excepción de código de operación indefinido (#UD).


Está mezclando bloqueos de alto nivel con la función de CPU de bajo nivel que se llamó LOCK .

Los bloqueos de alto nivel que los algoritmos sin bloqueo intentan evitar pueden proteger fragmentos de código arbitrarios cuya ejecución puede llevar un tiempo arbitrario y, por lo tanto, estos bloqueos tendrán que poner los hilos en estado de espera hasta que el bloqueo esté disponible, lo cual es una operación costosa, por ejemplo, implica manteniendo una cola de hilos en espera.

Esto es algo completamente diferente a la función de prefijo de LOCK CPU que protege una sola instrucción y, por lo tanto, puede contener otros subprocesos durante la duración de esa sola instrucción. Dado que esto lo implementa la propia CPU, no requiere esfuerzos de software adicionales.

Por lo tanto, el desafío de desarrollar algoritmos sin bloqueo no es la eliminación de la sincronización por completo, sino que se reduce para reducir la sección crítica del código a una sola operación atómica que será proporcionada por la propia CPU.


Parece que parte de lo que realmente estás preguntando es:

¿Por qué el prefijo de lock no está implícito para cmpxchg con un operando de memoria, como lo es para xchg ?

La respuesta simple (que otros han dado) es simplemente que Intel lo diseñó de esta manera. Pero esto lleva a la pregunta:

¿Por qué Intel hizo eso? ¿Existe un caso de uso para cmpxchg sin lock ?

En un sistema de CPU única, cmpxchg es atómico con respecto a otros subprocesos, o cualquier otro código que se ejecute en el mismo núcleo de CPU . (Pero no para los observadores del "sistema" como un dispositivo de E / S mapeado en memoria, o un dispositivo que hace lecturas DMA de memoria normal, por lo que lock cmpxchg era relevante incluso en diseños de CPU de un solo procesador).

Los cambios de contexto solo pueden ocurrir en interrupciones, y las interrupciones ocurren antes o después de una instrucción, no en el medio. Cualquier código que se ejecute en la misma CPU verá el cmpxchg como totalmente ejecutado o no ejecutado .

Por ejemplo, el kernel de Linux normalmente se compila con soporte SMP, por lo que utiliza lock cmpxchg para CAS atómico. Pero cuando se inicia en un sistema de un solo procesador, parcheará el prefijo de lock a un nop todas partes donde el código estaba en línea, ya que nop cmpxchg ejecuta mucho más rápido que lock cmpxchg . Para obtener más información, consulte este artículo de LWN sobre el sistema de "alternativas SMP" de Linux . Incluso puede volver a aplicar parches para lock prefijos antes de conectar en caliente una segunda CPU.

Lea más sobre la atomicidad de las instrucciones individuales en sistemas uniprocesadores en esta respuesta , y en la respuesta de @ supercat + comentarios sobre Can num++ be atomic for int num . Vea mi respuesta allí para obtener muchos detalles sobre cómo realmente funciona / se implementa lock cmpxchg para obtener instrucciones de lectura-modificación-escritura como lock cmpxchg .

(Este mismo razonamiento también se aplica a cmpxchg8b / cmpxchg16b y xadd , que generalmente solo se utilizan para operaciones de sincronización / atómicas, no para hacer que el código de un solo subproceso se ejecute más rápido. Obviamente, el destino de memoria add [mem], reg es útil fuera del lock add [mem], reg caso lock add [mem], reg .)