Estoy luchando con la Sección 5.1.2.4 del Estándar C11, en particular la semántica de Release / Acquire. Observo que https://preshing.com/20120913/acquire-and-release-semantics/ (entre otros) establece que:
... La semántica de liberación evita el reordenamiento de memoria de la liberación de escritura con cualquier operación de lectura o escritura que la preceda en orden de programa.
Entonces, para lo siguiente:
typedef struct test_struct
{
_Atomic(bool) ready ;
int v1 ;
int v2 ;
} test_struct_t ;
extern void
test_init(test_struct_t* ts, int v1, int v2)
{
ts->v1 = v1 ;
ts->v2 = v2 ;
atomic_store_explicit(&ts->ready, false, memory_order_release) ;
}
extern int
test_thread_1(test_struct_t* ts, int v2)
{
int v1 ;
while (atomic_load_explicit(&ts->ready, memory_order_acquire)) ;
ts->v2 = v2 ; // expect read to happen before store/release
v1 = ts->v1 ; // expect write to happen before store/release
atomic_store_explicit(&ts->ready, true, memory_order_release) ;
return v1 ;
}
extern int
test_thread_2(test_struct_t* ts, int v1)
{
int v2 ;
while (!atomic_load_explicit(&ts->ready, memory_order_acquire)) ;
ts->v1 = v1 ;
v2 = ts->v2 ; // expect write to happen after store/release in thread "1"
atomic_store_explicit(&ts->ready, false, memory_order_release) ;
return v2 ;
}
donde se ejecutan esos:
> in the "main" thread: test_struct_t ts ;
> test_init(&ts, 1, 2) ;
> start thread "2" which does: r2 = test_thread_2(&ts, 3) ;
> start thread "1" which does: r1 = test_thread_1(&ts, 4) ;
Por lo tanto, esperaría que el hilo "1" tenga r1 == 1 y el hilo "2" tenga r2 = 4.
Esperaría eso porque (siguiendo los párrafos 16 y 18 de la sección 5.1.2.4):
- todas las lecturas y escrituras (no atómicas) se "secuencian antes" y, por lo tanto, "suceden antes" de la escritura / liberación atómica en el hilo "1",
- que "entre hilos pasa antes" la lectura / adquisición atómica en el hilo "2" (cuando dice 'verdadero'),
- que a su vez está "secuenciado antes" y, por lo tanto, "sucede antes" de las lecturas y escrituras (no atómicas) (en el hilo "2").
Sin embargo, es totalmente posible que no haya entendido el estándar.
Observo que el código generado para x86_64 incluye:
test_thread_1:
movzbl (%rdi),%eax -- atomic_load_explicit(&ts->ready, memory_order_acquire)
test $0x1,%al
jne <test_thread_1> -- while is true
mov %esi,0x8(%rdi) -- (W1) ts->v2 = v2
mov 0x4(%rdi),%eax -- (R1) v1 = ts->v1
movb $0x1,(%rdi) -- (X1) atomic_store_explicit(&ts->ready, true, memory_order_release)
retq
test_thread_2:
movzbl (%rdi),%eax -- atomic_load_explicit(&ts->ready, memory_order_acquire)
test $0x1,%al
je <test_thread_2> -- while is false
mov %esi,0x4(%rdi) -- (W2) ts->v1 = v1
mov 0x8(%rdi),%eax -- (R2) v2 = ts->v2
movb $0x0,(%rdi) -- (X2) atomic_store_explicit(&ts->ready, false, memory_order_release)
retq
Y siempre que R1 y X1 sucedan en ese orden, esto da el resultado que espero.
Pero mi comprensión de x86_64 es que las lecturas ocurren en orden con otras lecturas y las escrituras ocurren en orden con otras escrituras, pero las lecturas y escrituras pueden no ocurrir en orden entre sí. Lo que implica que es posible que X1 suceda antes de R1, e incluso que X1, X2, W2, R1 sucedan en ese orden, creo. [Esto parece desesperadamente improbable, pero si R1 se retrasó por algunos problemas de caché?]
Por favor: ¿qué no estoy entendiendo?
Observo que si cambio las cargas / tiendas de ts->readya memory_order_seq_cst, el código generado para las tiendas es:
xchg %cl,(%rdi)
lo cual es consistente con mi comprensión de x86_64 y dará el resultado que espero.
fuente

8.2.3.3 Stores Are Not Reordered With Earlier Loads. Por lo tanto, su compilador está traduciendo correctamente su código (qué sorprendente), de modo que su código sea efectivamente completamente secuencial y no ocurra nada interesante al mismo tiempo.Respuestas:
El modelo de memoria de x86 es básicamente de consistencia secuencial más un búfer de tienda (con reenvío de tienda). Entonces cada tienda es una tienda de lanzamiento 1 . Es por eso que solo las tiendas seq-cst necesitan instrucciones especiales. ( C / C ++ 11 asignaciones atómicas a asm ). Además, https://stackoverflow.com/tags/x86/info tiene algunos enlaces a documentos x86, incluida una descripción formal del modelo de memoria x86-TSO (básicamente ilegible para la mayoría de los humanos; requiere leer muchas definiciones).
Como ya estás leyendo la excelente serie de artículos de Jeff Preshing, te señalaré otro que entra en más detalles: https://preshing.com/20120930/weak-vs-strong-memory-models/
El único reordenamiento permitido en x86 es StoreLoad, no LoadStore , si estamos hablando en esos términos. (El reenvío de la tienda puede hacer cosas extra divertidas si una carga solo se superpone parcialmente a una tienda; instrucciones de carga globalmente invisibles , aunque nunca obtendrá eso en el código generado por el compilador
stdatomic).@EOF comentó con la cita correcta del manual de Intel:
Nota 1: ignorar las tiendas NT mal ordenadas; Es por eso que normalmente
sfencedespués de hacer NT almacena. Las implementaciones de C11 / C ++ 11 suponen que no está utilizando tiendas NT. Si es así, úselo_mm_sfenceantes de una operación de lanzamiento para asegurarse de que respeta sus tiendas NT. (En general , no use_mm_mfence/_mm_sfenceen otros casos ; por lo general, solo necesita bloquear el reordenamiento en tiempo de compilación. O, por supuesto, solo use stdatomic).fuente