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
-
¿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? -
Si
CMPXCHG
no es atómico y se debe implementar un CAS de alto nivel de lenguaje a través deLOCK CMPXCHG
(con el prefijoLOCK
), ¿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 paracmpxchg
con un operando de memoria, como lo es paraxchg
?
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
sinlock
?
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
.)