C ++ std :: memory_order_relaxed confusión

Oct 12 2020

Estaba leyendo este artículo de GCC Wiki sobre las barreras de memoria de C ++ (y es increíble).
Fue bastante sencillo hasta que llegué a este punto:

El enfoque opuesto es std :: memory_order_relaxed. Este modelo permite mucha menos sincronización al eliminar las restricciones de suceder antes. Estos tipos de operaciones atómicas también pueden tener varias optimizaciones realizadas, como la eliminación de tiendas muertas y la puesta en común. Entonces, en el ejemplo anterior:

-Thread 1-
y.store (20, memory_order_relaxed)
x.store (10, memory_order_relaxed)

-Thread 2-
if (x.load (memory_order_relaxed) == 10)
  {
    assert (y.load(memory_order_relaxed) == 20) /* assert A */
    y.store (10, memory_order_relaxed)
  }

-Thread 3-
if (y.load (memory_order_relaxed) == 10)
  assert (x.load(memory_order_relaxed) == 10) /* assert B */

Dado que los subprocesos no necesitan estar sincronizados en todo el sistema, cualquiera de las afirmaciones de este ejemplo puede FALLAR.

Ok, esto también es sencillo, sigamos ...

-Thread 1-
x.store (1, memory_order_relaxed)
x.store (2, memory_order_relaxed)

-Thread 2-
y = x.load (memory_order_relaxed)
z = x.load (memory_order_relaxed)
assert (y <= z)

La afirmación no puede fallar. Una vez que el subproceso 2 ve el almacén de 2, ya no puede ver el valor 1. Esto evita la fusión de cargas relajadas de una variable en cargas relajadas de una referencia diferente que podría alias.

Esto es lo que me confundió, ¿por qué y no puede cargar el valor 2 yz cargar el valor 1 (y hacer que la aserción falle), ya que el orden no está sincronizado en el hilo 1?

Respuestas

4 NicolBolas Oct 12 2020 at 12:12

El orden relajado es relativo al orden de las operaciones con respecto a otros accesos a la memoria , no el orden relativo al atómico que se modifica de forma relajada . En su primer caso, el hecho de que pueda ver 10 en xno significa nada con respecto al valor de y. Y viceversa.

Pero su segundo caso es diferente, porque afecta al mismo objeto atómico.

[intro.races] / 10 nos dice que, dentro de un hilo, si una operación se secuencia antes que otra, esa operación "ocurre antes" que la otra. Y [intro.races] / 14-17 describe el siguiente comportamiento con respecto a la atómica :

Los cuatro requisitos de coherencia anteriores impiden efectivamente el reordenamiento del compilador de operaciones atómicas en un solo objeto, incluso si ambas operaciones son cargas relajadas.

Y eso es lo que tienes aquí. Todas las modificaciones están sucediendo en el mismo objeto, por lo que deben ocurrir en algún orden. Incluso si ese orden no se puede determinar con exactitud, el pedido debe respetar las relaciones "sucede antes" del código.

Las dos operaciones del hilo 1 están ordenadas por una relación "sucede antes". Y las operaciones de Thread 2 están ordenadas por una relación "sucede antes".

Dado que todos actúan sobre el mismo objeto atómico, si yobtiene el valor 2, entonces debe haber "sucedido después de" que xse estableció en 2. Por lo tanto, el orden de acceso para xdebe haber sido "x = 1, x = 2, leer x ". Y dado que la última lectura de xocurre después de la primera lectura de x, el valor que obtiene no puede ser 1.