En la documentación de std::memory_order
cppreference.com hay un ejemplo de pedido relajado:
Pedidos relajados
Las operaciones atómicas etiquetadas
memory_order_relaxed
no son operaciones de sincronización; no imponen un orden entre los accesos concurrentes de memoria. Solo garantizan la atomicidad y la coherencia del orden de modificación.Por ejemplo, con x e y inicialmente cero,
// Thread 1: r1 = y.load(std::memory_order_relaxed); // A x.store(r1, std::memory_order_relaxed); // B // Thread 2: r2 = x.load(std::memory_order_relaxed); // C y.store(42, std::memory_order_relaxed); // D
se permite producir r1 == r2 == 42 porque, aunque A se secuencia antes de B dentro del hilo 1 y C se secuencia antes de D dentro del hilo 2, nada impide que D aparezca antes de A en el orden de modificación de y, y B de apareciendo antes de C en el orden de modificación de x. El efecto secundario de D en y podría ser visible para la carga A en el subproceso 1, mientras que el efecto secundario de B en x podría ser visible para la carga C en el subproceso 2. En particular, esto puede ocurrir si D se completa antes de C en subproceso 2, ya sea debido a la reordenación del compilador o en tiempo de ejecución.
dice "C está secuenciado antes que D dentro del hilo 2".
De acuerdo con la definición de secuenciado antes, que se puede encontrar en el Orden de evaluación , si A se secuencia antes que B, la evaluación de A se completará antes de que comience la evaluación de B. Dado que C se secuencia antes de D dentro del hilo 2, C debe completarse antes de que D comience, por lo tanto, la parte de condición de la última oración de la instantánea nunca se cumplirá.
fuente
Respuestas:
Creo que la preferencia es correcta. Creo que esto se reduce a la regla "como si" [intro.execution] / 1 . Los compiladores solo están obligados a reproducir el comportamiento observable del programa descrito por su código. Una relación secuenciada antes solo se establece entre evaluaciones desde la perspectiva del hilo en el que se realizan estas evaluaciones [intro.execution] / 15 . Eso significa que cuando dos evaluaciones secuenciadas una después de la otra aparecen en algún subproceso, el código que realmente se ejecuta en ese subproceso debe comportarse como si lo que fuera que hiciera la primera evaluación realmente afectara lo que haga la segunda evaluación. Por ejemplo
debe imprimir 42. Sin embargo, el compilador en realidad no tiene que almacenar el valor 42 en un objeto
x
antes de leer el valor de ese objeto para imprimirlo. También puede recordar que el último valor que se almacenóx
fue 42 y luego simplemente imprimir el valor 42 directamente antes de hacer un almacenamiento real del valor 42x
. De hecho, six
es una variable local, también puede rastrear qué valor se asignó a esa variable por última vez en cualquier punto y nunca crear un objeto o almacenar el valor 42. No hay forma de que el hilo marque la diferencia. El comportamiento siempre será como si hubiera una variable y como si el valor 42 estuviera realmente almacenado en un objetox
antessiendo cargado desde ese objeto. Pero eso no significa que el código de máquina generado tenga que almacenar y cargar cualquier cosa en cualquier lugar. Todo lo que se requiere es que el comportamiento observable del código de máquina generado sea indistinguible de cuál sería el comportamiento si todas estas cosas realmente sucedieran.Si nos fijamos en
entonces sí, C se secuencia antes que D. Pero cuando se ve desde este hilo de forma aislada, nada de lo que C afecta el resultado de D. Y nada de lo que hace D cambiaría el resultado de C. La única forma en que uno podría afectar al otro sería como consecuencia indirecta de que algo suceda en otro hilo. Sin embargo, al especificar
std::memory_order_relaxed
, usted declaró explícitamenteque el orden en que la carga y el almacenamiento son observados por otro hilo es irrelevante. Como ningún otro subproceso puede observar la carga y el almacenamiento en un orden particular, no hay nada que otro subproceso pueda hacer para que C y D se afecten entre sí de manera consistente. Por lo tanto, el orden en que se realizan realmente la carga y el almacenamiento es irrelevante. Por lo tanto, el compilador es libre de reordenarlos. Y, como se menciona en la explicación debajo de ese ejemplo, si el almacenamiento de D se realiza antes de la carga de C, entonces r1 == r2 == 42 puede ocurrir ...fuente
A veces es posible ordenar una acción en relación con otras dos secuencias de acciones, sin implicar ningún orden relativo de las acciones en esas secuencias entre sí.
Supongamos, por ejemplo, que uno tiene los siguientes tres eventos:
y la lectura de p2 se ordena independientemente después de la escritura de p1 y antes de la escritura de p3, pero no existe un orden particular en el que participen tanto p1 como p3. Dependiendo de lo que se haga con p2, puede ser poco práctico para un compilador diferir p1 más allá de p3 y aún así lograr la semántica requerida con p2. Supongamos, sin embargo, que el compilador sabía que el código anterior era parte de una secuencia más grande:
En ese caso, podría determinar que podría reordenar la tienda a p1 después del código anterior y consolidarla con la siguiente tienda, lo que da como resultado un código que escribe p3 sin escribir p1 primero:
Aunque parezca que las dependencias de datos causarían que ciertas partes de las relaciones de secuenciación se comporten de forma transitiva, un compilador puede identificar situaciones en las que no existen dependencias de datos aparentes y, por lo tanto, no tendría los efectos transitivos que uno esperaría.
fuente
Si hay dos declaraciones, el compilador generará código en orden secuencial, por lo que el código de la primera se colocará antes de la segunda. Pero los cpus tienen canales internos y pueden ejecutar operaciones de ensamblaje en paralelo. La declaración C es una instrucción de carga. Mientras se recupera la memoria, la canalización procesará las siguientes instrucciones y, dado que no dependen de la instrucción de carga, podrían terminar ejecutándose antes de que C finalice (por ejemplo, los datos de D estaban en caché, C en la memoria principal).
Si el usuario realmente necesita que las dos instrucciones se ejecuten secuencialmente, se pueden usar operaciones de ordenamiento de memoria más estrictas. En general, a los usuarios no les importa mientras el programa sea lógicamente correcto.
fuente
Lo que creas es igualmente válido. El estándar no dice qué se ejecuta secuencialmente, qué no y cómo se puede mezclar .
Depende de usted, y de cada programador, crear una semántica consistente además de ese desastre, un trabajo digno de múltiples doctorados.
fuente