He escuchado y leído varios artículos, charlas y preguntas sobre el stackoverflow std::atomic
, y me gustaría asegurarme de haberlo entendido bien. Debido a que todavía estoy un poco confundido con la línea de caché, la visibilidad de las escrituras se debe a posibles retrasos en los protocolos de coherencia de caché MESI (o derivados), almacenar buffers, invalidar colas, etc.
Leí que x86 tiene un modelo de memoria más fuerte, y que si se retrasa la invalidación de la memoria caché, x86 puede revertir las operaciones iniciadas. Pero ahora estoy interesado solo en lo que debería asumir como programador de C ++, independientemente de la plataforma.
[T1: thread1 T2: thread2 V1: variable atómica compartida]
Entiendo que std :: atomic garantiza que,
(1) No se producen carreras de datos en una variable (gracias al acceso exclusivo a la línea de caché).
(2) Dependiendo de qué orden de memoria usamos, garantiza (con barreras) que se produce una coherencia secuencial (antes de una barrera, después de una barrera o ambas).
(3) Después de una escritura atómica (V1) en T1, un RMW atómico (V1) en T2 será coherente (su línea de caché se habrá actualizado con el valor escrito en T1).
Pero como se menciona en la cartilla de coherencia de caché ,
La implicación de todas estas cosas es que, por defecto, las cargas pueden obtener datos obsoletos (si una solicitud de invalidación correspondiente estaba en la cola de invalidación)
Entonces, ¿es correcto lo siguiente?
(4) std::atomic
NO garantiza que T2 no leerá un valor 'rancio' en una lectura atómica (V) después de una escritura atómica (V) en T1.
Se pregunta si (4) es correcto: si la escritura atómica en T1 invalida la línea de caché sin importar el retraso, ¿por qué T2 está esperando que la invalidación sea efectiva cuando una operación RMW atómica pero no en una lectura atómica?
Pregunta si (4) está mal: ¿cuándo puede un hilo leer un valor 'rancio' y "es visible" en la ejecución, entonces?
Agradezco mucho tus respuestas
Actualización 1
Entonces parece que me equivoqué en (3) entonces. Imagine la siguiente intercalación, para un V1 inicial = 0:
T1: W(1)
T2: R(0) M(++) W(1)
Aunque se garantiza que el RMW de T2 sucederá completamente después de W (1) en este caso, aún puede leer un valor 'rancio' (estaba equivocado). De acuerdo con esto, atomic no garantiza la coherencia completa de la memoria caché, solo la coherencia secuencial.
Actualización 2
(5) Ahora imagine este ejemplo (x = y = 0 y son atómicos):
T1: x = 1;
T2: y = 1;
T3: if (x==1 && y==0) print("msg");
De acuerdo con lo que hemos hablado, ver el "mensaje" que se muestra en la pantalla no nos daría información más allá de que T2 se ejecutó después de T1. Entonces, cualquiera de las siguientes ejecuciones podría haber sucedido:
- T1 <T3 <T2
- T1 <T2 <T3 (donde T3 ve x = 1 pero no y = 1 todavía)
¿está bien?
(6) Si un hilo siempre puede leer valores "obsoletos", ¿qué sucedería si tomáramos el escenario típico de "publicación" pero en lugar de indicar que algunos datos están listos, hacemos lo contrario (eliminar los datos)?
T1: delete gameObjectPtr; is_enabled.store(false, std::memory_order_release);
T2: while (is_enabled.load(std::memory_order_acquire)) gameObjectPtr->doSomething();
donde T2 todavía estaría usando un ptr eliminado hasta que vea que is_enabled es falso.
(7) Además, el hecho de que los subprocesos puedan leer valores "obsoletos" significa que un mutex no se puede implementar con un solo derecho atómico sin bloqueo. Requeriría un mecanismo de sincronización entre hilos. ¿Requeriría un atómico bloqueable?