std:: memory_order
|
Definido en el encabezado
<atomic>
|
||
|
enum
memory_order
{
|
(desde C++11)
(hasta C++20) |
|
|
enum
class
memory_order
:
/* no especificado */
{
|
(desde C++20) | |
std::memory_order
especifica cómo se deben ordenar los accesos a memoria, incluidos los accesos regulares no atómicos, alrededor de una operación atómica. Sin restricciones en un sistema multinúcleo, cuando múltiples hilos leen y escriben simultáneamente en varias variables, un hilo puede observar que los valores cambian en un orden diferente al orden en que otro hilo los escribió. De hecho, el orden aparente de los cambios puede incluso diferir entre múltiples hilos lectores. Algunos efectos similares pueden ocurrir incluso en sistemas monoprocesador debido a transformaciones del compilador permitidas por el modelo de memoria.
El comportamiento predeterminado de todas las operaciones atómicas en la biblioteca proporciona un
ordenamiento secuencialmente consistente
(ver discusión abajo). Ese valor predeterminado puede afectar el rendimiento, pero a las operaciones atómicas de la biblioteca se les puede dar un argumento adicional
std::memory_order
para especificar las restricciones exactas, más allá de la atomicidad, que el compilador y el procesador deben hacer cumplir para esa operación.
Constantes
|
Definido en el encabezado
<atomic>
|
|
| Nombre | Significado |
memory_order_relaxed
|
Operación relajada: no se imponen restricciones de sincronización u ordenamiento en otras lecturas o escrituras, solo se garantiza la atomicidad de esta operación (ver Ordenamiento relajado abajo). |
memory_order_consume
(obsoleto en C++26) |
Una operación de carga con este orden de memoria realiza una operación de consumo en la ubicación de memoria afectada: ninguna lectura o escritura en el hilo actual dependiente del valor cargado actualmente puede reordenarse antes de esta carga. Las escrituras a variables dependientes de datos en otros hilos que liberan la misma variable atómica son visibles en el hilo actual. En la mayoría de plataformas, esto afecta solo a las optimizaciones del compilador (ver Ordenamiento liberación-consumo abajo). |
memory_order_acquire
|
Una operación de carga con este orden de memoria realiza la operación de adquisición en la ubicación de memoria afectada: ninguna lectura o escritura en el hilo actual puede reordenarse antes de esta carga. Todas las escrituras en otros hilos que liberan la misma variable atómica son visibles en el hilo actual (ver Ordenamiento liberación-adquisición abajo). |
memory_order_release
|
Una operación de almacenamiento con este orden de memoria realiza la operación de liberación : ninguna lectura o escritura en el hilo actual puede reordenarse después de este almacenamiento. Todas las escrituras en el hilo actual son visibles en otros hilos que adquieren la misma variable atómica (ver Ordenamiento liberación-adquisición abajo) y las escrituras que llevan una dependencia a la variable atómica se vuelven visibles en otros hilos que consumen la misma variable atómica (ver Ordenamiento liberación-consumo abajo). |
memory_order_acq_rel
|
Una operación de lectura-modificación-escritura con este orden de memoria es tanto una operación de adquisición como una operación de liberación . Ninguna lectura o escritura de memoria en el hilo actual puede reordenarse antes de la carga, ni después del almacenamiento. Todas las escrituras en otros hilos que liberan la misma variable atómica son visibles antes de la modificación y la modificación es visible en otros hilos que adquieren la misma variable atómica. |
memory_order_seq_cst
|
Una operación de carga con este orden de memoria realiza una operación de adquisición , un almacenamiento realiza una operación de liberación , y lectura-modificación-escritura realiza tanto una operación de adquisición como una operación de liberación , además existe un único orden total en el cual todos los hilos observan todas las modificaciones en el mismo orden (ver Ordenamiento secuencialmente consistente abajo). |
Descripción formal
La sincronización entre hilos y el ordenamiento de memoria determinan cómo se ordenan las evaluaciones y los efectos secundarios de las expresiones entre diferentes hilos de ejecución. Se definen en los siguientes términos:
Secuenciado-antes
Dentro del mismo hilo, la evaluación A puede estar sequenced-before la evaluación B, como se describe en evaluation order .
Transmisión de dependenciaDentro del mismo hilo, la evaluación A que está secuenciada-antes de la evaluación B también puede transmitir una dependencia a B (es decir, B depende de A), si se cumple alguna de las siguientes condiciones:
1)
El valor de A se utiliza como operando de B,
excepto
a)
si B es una llamada a
std::kill_dependency
,
b)
si A es el operando izquierdo de los operadores incorporados
&&
,
||
,
?:
, o
,
.
2)
A escribe en un objeto escalar M, B lee de M.
3)
A transmite dependencia a otra evaluación X, y X transmite dependencia a B.
|
(hasta C++26) |
Orden de modificación
Todas las modificaciones a cualquier variable atómica particular ocurren en un orden total que es específico para esta única variable atómica.
Las siguientes cuatro garantías se aplican a todas las operaciones atómicas:
Secuencia de liberación
Después de que se realice una operación de liberación A sobre un objeto atómico M, la subsecuencia continua más larga del orden de modificación de M que consiste en:
|
1)
Escrituras realizadas por el mismo hilo que realizó A.
|
(until C++20) |
Se conoce como release sequence headed by A .
Se sincroniza con
Si un almacenamiento atómico en el hilo A es una operación de liberación , una carga atómica en el hilo B de la misma variable es una operación de adquisición , y la carga en el hilo B lee un valor escrito por el almacenamiento en el hilo A, entonces el almacenamiento en el hilo A se sincroniza con la carga en el hilo B.
Además, algunas llamadas de biblioteca pueden estar definidas para synchronize-with otras llamadas de biblioteca en otros hilos.
Ordenado por dependencia antesEntre hilos, la evaluación A está ordenada por dependencia antes de la evaluación B si cualquiera de las siguientes condiciones es verdadera:
1)
A realiza una
operación de liberación
en algún atómico M, y, en un hilo diferente, B realiza una
operación de consumo
en el mismo atómico M, y B lee un valor escrito
por cualquier parte de la secuencia de liberación encabezada
(hasta C++20)
por A.
2)
A está ordenado por dependencia antes de X y X lleva una dependencia a B.
|
(hasta C++26) |
Sucede-antes entre hilos
Entre hilos, la evaluación A inter-thread happens before la evaluación B si cualquiera de las siguientes condiciones es verdadera:
Happens-beforeIndependientemente de los hilos, la evaluación A happens-before la evaluación B si cualquiera de las siguientes es verdadera:
1)
A está
sequenced-before
B.
2)
A
inter-thread happens before
B.
Se requiere que la implementación garantice que la relación happens-before sea acíclica, introduciendo sincronización adicional si es necesario (solo puede ser necesario si está involucrada una operación consume, ver Batty et al ). Si una evaluación modifica una ubicación de memoria, y la otra lee o modifica la misma ubicación de memoria, y si al menos una de las evaluaciones no es una operación atómica, el comportamiento del programa es indefinido (el programa tiene una data race ) a menos que exista una relación happens-before entre estas dos evaluaciones.
|
(until C++26) | ||
Happens-beforeIndependientemente de los hilos, la evaluación A happens-before la evaluación B si cualquiera de las siguientes es verdadera:
1)
A está
sequenced-before
B.
2)
A
synchronizes-with
B.
3)
A
happens-before
X, y X
happens-before
B.
|
(since C++26) |
Sucede fuertemente antes
Independientemente de los hilos, la evaluación A strongly happens-before la evaluación B si cualquiera de las siguientes condiciones es verdadera:
|
1)
A está
sequenced-before
B.
2)
A
synchronizes-with
B.
3)
A
strongly happens-before
X, y X
strongly happens-before
B.
|
(hasta C++20) | ||
|
1)
A está
sequenced-before
B.
2)
A
synchronizes with
B, y tanto A como B son operaciones atómicas secuencialmente consistentes.
3)
A está
sequenced-before
X, X
simplemente
(hasta C++26)
happens-before
Y, e Y está
sequenced-before
B.
4)
A
strongly happens-before
X, y X
strongly happens-before
B.
Nota: informalmente, si A strongly happens-before B, entonces A parece evaluarse antes que B en todos los contextos.
|
(desde C++20) |
Efectos secundarios visibles
El efecto secundario A sobre un escalar M (una escritura) es visible con respecto al cálculo de valor B sobre M (una lectura) si ambas condiciones siguientes son verdaderas:
Si el efecto secundario A es visible con respecto al cálculo de valor B, entonces el subconjunto contiguo más largo de los efectos secundarios sobre M, en orden de modificación , donde B no sucede-antes de él se conoce como la secuencia visible de efectos secundarios (el valor de M, determinado por B, será el valor almacenado por uno de estos efectos secundarios).
Nota: la sincronización entre hilos se reduce a prevenir carreras de datos (estableciendo relaciones de sucede-antes) y definir qué efectos secundarios se vuelven visibles bajo qué condiciones.
Operación de consumo
La carga atómica con
memory_order_consume
o más fuerte es una operación de consumo. Nótese que
std::atomic_thread_fence
impone requisitos de sincronización más fuertes que una operación de consumo.
Operación de adquisición
La carga atómica con
memory_order_acquire
o más fuerte es una operación de adquisición. La operación
lock()
en un
Mutex
también es una operación de adquisición. Nótese que
std::atomic_thread_fence
impone requisitos de sincronización más fuertes que una operación de adquisición.
Operación de liberación
La operación de almacenamiento atómico con
memory_order_release
o más fuerte es una operación de liberación. La operación
unlock()
en un
Mutex
también es una operación de liberación. Nótese que
std::atomic_thread_fence
impone requisitos de sincronización más fuertes que una operación de liberación.
Explicación
Ordenamiento relajado
Las operaciones atómicas etiquetadas con memory_order_relaxed no son operaciones de sincronización; no imponen un orden entre los accesos concurrentes a memoria. Solo garantizan atomicidad y consistencia del orden de modificación.
Por ejemplo, con x y y inicialmente en cero,
// Hilo 1: r1 = y.load(std::memory_order_relaxed); // A x.store(r1, std::memory_order_relaxed); // B // Hilo 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 está secuenciado-antes de B dentro del hilo 1 y C está secuenciado antes de D dentro del hilo 2, nada impide que D aparezca antes que A en el orden de modificación de y , y que B aparezca antes que 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 hilo 1 mientras que el efecto secundario de B en x podría ser visible para la carga C en el hilo 2. En particular, esto puede ocurrir si D se completa antes que C en el hilo 2, ya sea debido a reordenamiento del compilador o durante la ejecución.
|
Incluso con el modelo de memoria relajado, no se permiten valores "de la nada" que dependan circularmente de sus propios cálculos, por ejemplo, con x e y inicialmente en cero, // Thread 1: r1 = y.load(std::memory_order_relaxed); if (r1 == 42) x.store(r1, std::memory_order_relaxed); // Thread 2: r2 = x.load(std::memory_order_relaxed); if (r2 == 42) y.store(42, std::memory_order_relaxed); no se permite producir r1 == r2 == 42 ya que el almacenamiento de 42 en y solo es posible si el almacenamiento en x almacena 42 , lo cual depende circularmente del almacenamiento en y que almacena 42 . Nótese que hasta C++14, esto estaba técnicamente permitido por la especificación, pero no se recomendaba a los implementadores. |
(desde C++14) |
Un uso típico para el orden de memoria relajado es incrementar contadores, como los contadores de referencia de
std::shared_ptr
, ya que esto solo requiere atomicidad, pero no ordenamiento o sincronización (nótese que decrementar los
std::shared_ptr
contadores requiere sincronización de adquisición-liberación con el destructor).
#include <atomic> #include <iostream> #include <thread> #include <vector> std::atomic<int> cnt = {0}; void f() { for (int n = 0; n < 1000; ++n) cnt.fetch_add(1, std::memory_order_relaxed); } int main() { std::vector<std::thread> v; for (int n = 0; n < 10; ++n) v.emplace_back(f); for (auto& t : v) t.join(); std::cout << "Final counter value is " << cnt << '\n'; }
Salida:
Final counter value is 10000
Ordenamiento Liberar-Adquirir
Si un almacenamiento atómico en el hilo A está etiquetado como memory_order_release , una carga atómica en el hilo B de la misma variable está etiquetada como memory_order_acquire , y la carga en el hilo B lee un valor escrito por el almacenamiento en el hilo A, entonces el almacenamiento en el hilo A sincroniza-con la carga en el hilo B.
Todas las escrituras de memoria (incluyendo las no atómicas y atómicas relajadas) que happened-before del almacenamiento atómico desde el punto de vista del hilo A, se convierten en visible side-effects en el hilo B. Es decir, una vez que se completa la carga atómica, se garantiza que el hilo B verá todo lo que el hilo A escribió en la memoria. Esta promesa solo se cumple si B realmente devuelve el valor que A almacenó, o un valor posterior en la secuencia de liberación.
La sincronización se establece únicamente entre los hilos que liberan y que adquieren la misma variable atómica. Otros hilos pueden ver un orden diferente de accesos a memoria que cualquiera o ambos de los hilos sincronizados.
En sistemas fuertemente ordenados — x86, SPARC TSO, mainframe de IBM, etc. — el ordenamiento release-acquire es automático para la mayoría de operaciones. No se emiten instrucciones adicionales de CPU para este modo de sincronización; solo ciertas optimizaciones del compilador se ven afectadas (por ejemplo, se prohíbe al compilador mover almacenamientos no atómicos más allá del almacenamiento atómico-release o realizar cargas no atómicas antes que la carga atómica-acquire). En sistemas débilmente ordenados (ARM, Itanium, PowerPC), se utilizan instrucciones especiales de carga de CPU o barreras de memoria.
Los bloqueos de exclusión mutua, como std::mutex o atomic spinlock , son un ejemplo de sincronización de liberación-adquisición: cuando el bloqueo es liberado por el hilo A y adquirido por el hilo B, todo lo que ocurrió en la sección crítica (antes de la liberación) en el contexto del hilo A debe ser visible para el hilo B (después de la adquisición) que está ejecutando la misma sección crítica.
#include <atomic> #include <cassert> #include <string> #include <thread> std::atomic<std::string*> ptr; int data; void producer() { std::string* p = new std::string("Hello"); data = 42; ptr.store(p, std::memory_order_release); } void consumer() { std::string* p2; while (!(p2 = ptr.load(std::memory_order_acquire))) ; assert(*p2 == "Hello"); // nunca falla assert(data == 42); // nunca falla } int main() { std::thread t1(producer); std::thread t2(consumer); t1.join(); t2.join(); }
El siguiente ejemplo demuestra el ordenamiento transitivo liberación-adquisición a través de tres hilos, utilizando una secuencia de liberación.
#include <atomic> #include <cassert> #include <thread> #include <vector> std::vector<int> data; std::atomic<int> flag = {0}; void thread_1() { data.push_back(42); flag.store(1, std::memory_order_release); } void thread_2() { int expected = 1; // memory_order_relaxed is okay because this is an RMW, // and RMWs (with any ordering) following a release form a release sequence while (!flag.compare_exchange_strong(expected, 2, std::memory_order_relaxed)) { expected = 1; } } void thread_3() { while (flag.load(std::memory_order_acquire) < 2) ; // if we read the value 2 from the atomic flag, we see 42 in the vector assert(data.at(0) == 42); // will never fire } int main() { std::thread a(thread_1); std::thread b(thread_2); std::thread c(thread_3); a.join(); b.join(); c.join(); }
Ordenamiento Liberar-Consumir
|
Si un almacenamiento atómico en el hilo A está etiquetado como memory_order_release , una carga atómica en el hilo B de la misma variable está etiquetada como memory_order_consume , y la carga en el hilo B lee un valor escrito por el almacenamiento en el hilo A, entonces el almacenamiento en el hilo A está ordenado por dependencia antes de la carga en el hilo B. Todas las escrituras de memoria (no atómicas y atómicas relajadas) que ocurrieron antes del almacenamiento atómico desde el punto de vista del hilo A, se convierten en efectos secundarios visibles dentro de aquellas operaciones en el hilo B en las que la operación de carga transporta dependencia , es decir, una vez que se completa la carga atómica, se garantiza que aquellos operadores y funciones en el hilo B que utilizan el valor obtenido de la carga verán lo que el hilo A escribió en la memoria. La sincronización se establece únicamente entre los hilos que liberan y consumen la misma variable atómica. Otros hilos pueden ver un orden diferente de accesos a memoria que cualquiera o ambos de los hilos sincronizados. En todas las CPU principales excepto DEC Alpha, el ordenamiento por dependencia es automático; no se emiten instrucciones adicionales de CPU para este modo de sincronización, solo se ven afectadas ciertas optimizaciones del compilador (por ejemplo, se prohíbe al compilador realizar cargas especulativas en los objetos que forman parte de la cadena de dependencia).
Los casos de uso típicos para este ordenamiento implican acceso de lectura a estructuras de datos concurrentes poco modificadas (tablas de enrutamiento, configuración, políticas de seguridad, reglas de firewall, etc.) y situaciones de publicador-suscriptor con publicación mediada por punteros, es decir, cuando el productor publica un puntero a través del cual el consumidor puede acceder a la información: no es necesario hacer visible todo lo demás que el productor escribió en la memoria al consumidor (lo cual puede ser una operación costosa en arquitecturas débilmente ordenadas). Un ejemplo de tal escenario es
Véase también
std::kill_dependency
y
Nótese que actualmente (2/2015) ningún compilador de producción conocido rastrea cadenas de dependencia: las operaciones de consumo se elevan a operaciones de adquisición. |
(until C++26) |
|
La especificación del ordenamiento release-consume está siendo revisada, y se desaconseja temporalmente el uso de
|
(since C++17)
(until C++26) |
|
El ordenamiento release-consume tiene el mismo efecto que el ordenamiento release-acquire y está obsoleto. |
(since C++26) |
Este ejemplo demuestra la sincronización ordenada por dependencia para la publicación mediada por puntero: el entero data no está relacionado con el puntero a string por una relación de dependencia de datos, por lo tanto su valor es indefinido en el consumidor.
#include <atomic> #include <cassert> #include <string> #include <thread> std::atomic<std::string*> ptr; int data; void producer() { std::string* p = new std::string("Hello"); data = 42; ptr.store(p, std::memory_order_release); } void consumer() { std::string* p2; while (!(p2 = ptr.load(std::memory_order_consume))) ; assert(*p2 == "Hello"); // never fires: *p2 carries dependency from ptr assert(data == 42); // may or may not fire: data does not carry dependency from ptr } int main() { std::thread t1(producer); std::thread t2(consumer); t1.join(); t2.join(); }
Ordenamiento secuencialmente consistente
Las operaciones atómicas etiquetadas con memory_order_seq_cst no solo ordenan la memoria de la misma manera que el ordenamiento de liberación/adquisición (todo lo que ocurrió-antes de un almacenamiento en un hilo se convierte en un efecto secundario visible en el hilo que realizó una carga), sino que también establecen un orden de modificación total único de todas las operaciones atómicas que están etiquetadas de esta manera.
|
Formalmente,
cada operación
Si existía una operación
Para un par de operaciones atómicas en M llamadas A y B, donde A escribe y B lee el valor de M, si existen dos
Para un par de modificaciones atómicas de M llamadas A y B, B ocurre después de A en el orden de modificación de M si
Nótese que esto significa que:
1)
tan pronto como operaciones atómicas que no están etiquetadas como
memory_order_seq_cst
entran en escena, se pierde la consistencia secuencial,
2)
las barreras secuencialmente consistentes solo establecen un orden total para las barreras mismas, no para las operaciones atómicas en el caso general (
sequenced-before
no es una relación entre hilos, a diferencia de
happens-before
).
|
(until C++20) |
|
Formalmente,
una operación atómica A sobre algún objeto atómico M está ordenada-por-coherencia-antes de otra operación atómica B sobre M si se cumple alguna de las siguientes condiciones:
1)
A es una modificación, y B lee el valor almacenado por A,
2)
A precede a B en el
orden de modificación
de M,
3)
A lee el valor almacenado por una modificación atómica X, X precede a B en el
orden de modificación
, y A y B no son la misma operación atómica de lectura-modificación-escritura,
4)
A está
ordenada-por-coherencia-antes
de X, y X está
ordenada-por-coherencia-antes
de B.
Existe un único orden total S en todas las operaciones
1)
si A y B son operaciones
memory_order_seq_cst
, y A
fuertemente precede-en-tiempo
a B, entonces A precede a B en S,
2)
para cada par de operaciones atómicas A y B sobre un objeto M, donde A está
ordenada-por-coherencia-antes
de B:
a)
si A y B son ambas operaciones
memory_order_seq_cst
, entonces A precede a B en S,
b)
si A es una operación
memory_order_seq_cst
, y B
precede-en-tiempo
a una barrera
memory_order_seq_cst
Y, entonces A precede a Y en S,
c)
si una barrera
memory_order_seq_cst
X
precede-en-tiempo
a A, y B es una operación
memory_order_seq_cst
, entonces X precede a B en S,
d)
si una barrera
memory_order_seq_cst
X
precede-en-tiempo
a A, y B
precede-en-tiempo
a una barrera
memory_order_seq_cst
Y, entonces X precede a Y en S.
La definición formal asegura que:
1)
el único orden total es consistente con el
orden de modificación
de cualquier objeto atómico,
2)
una carga
memory_order_seq_cst
obtiene su valor ya sea de la última modificación
memory_order_seq_cst
, o de alguna modificación no-
memory_order_seq_cst
que no
precede-en-tiempo
a modificaciones
memory_order_seq_cst
anteriores.
El único orden total podría no ser consistente con
precede-en-tiempo
. Esto permite una implementación más eficiente de
Por ejemplo, con
// Hilo 1: x.store(1, std::memory_order_seq_cst); // A y.store(1, std::memory_order_release); // B // Hilo 2: r1 = y.fetch_add(1, std::memory_order_seq_cst); // C r2 = y.load(std::memory_order_relaxed); // D // Hilo 3: y.store(3, std::memory_order_seq_cst); // E r3 = x.load(std::memory_order_seq_cst); // F
puede producir
r1
==
1
&&
r2
==
3
&&
r3
==
0
, donde A
precede-en-tiempo
a C, pero C precede a A en el único orden total C-E-F-A de
Nótese que:
1)
tan pronto como operaciones atómicas que no están etiquetadas como
memory_order_seq_cst
entran en escena, se pierde la garantía de consistencia secuencial para el programa,
2)
en muchos casos, las operaciones atómicas
memory_order_seq_cst
pueden reordenarse
con respecto a otras operaciones atómicas realizadas por el mismo hilo.
|
(desde C++20) |
La ordenación secuencial puede ser necesaria en situaciones de múltiples productores-múltiples consumidores donde todos los consumidores deben observar las acciones de todos los productores ocurriendo en el mismo orden.
La ordenación secuencial total requiere una instrucción de barrera de memoria completa de la CPU en todos los sistemas multinúcleo. Esto puede convertirse en un cuello de botella de rendimiento ya que fuerza a que los accesos a memoria afectados se propaguen a cada núcleo.
Este ejemplo demuestra una situación donde el orden secuencial es necesario. Cualquier otro orden podría activar el assert porque sería posible que los hilos
c
y
d
observen los cambios en los atómicos
x
y
y
en orden opuesto.
#include <atomic> #include <cassert> #include <thread> std::atomic<bool> x = {false}; std::atomic<bool> y = {false}; std::atomic<int> z = {0}; void write_x() { x.store(true, std::memory_order_seq_cst); } void write_y() { y.store(true, std::memory_order_seq_cst); } void read_x_then_y() { while (!x.load(std::memory_order_seq_cst)) ; if (y.load(std::memory_order_seq_cst)) ++z; } void read_y_then_x() { while (!y.load(std::memory_order_seq_cst)) ; if (x.load(std::memory_order_seq_cst)) ++z; } int main() { std::thread a(write_x); std::thread b(write_y); std::thread c(read_x_then_y); std::thread d(read_y_then_x); a.join(); b.join(); c.join(); d.join(); assert(z.load() != 0); // will never happen }
Relación con volatile
Dentro de un hilo de ejecución, los accesos (lecturas y escrituras) a través de volatile glvalues no pueden ser reordenados más allá de efectos secundarios observables (incluyendo otros accesos volátiles) que estén sequenced-before o sequenced-after dentro del mismo hilo, pero este orden no está garantizado que sea observado por otro hilo, ya que el acceso volátil no establece sincronización entre hilos.
Además, los accesos volátiles no son atómicos (la lectura y escritura concurrente es una data race ) y no ordenan la memoria (los accesos a memoria no volátil pueden reordenarse libremente alrededor del acceso volátil).
Una excepción notable es Visual Studio, donde, con la configuración predeterminada, cada escritura volátil tiene semántica de liberación y cada lectura volátil tiene semántica de adquisición (
Microsoft Docs
), y por lo tanto los volátiles pueden usarse para sincronización entre hilos. La semántica estándar de
volatile
no es aplicable a programación multiproceso, aunque son suficientes para, por ejemplo, comunicación con un
std::signal
handler que se ejecuta en el mismo hilo cuando se aplica a variables
sig_atomic_t
. La opción del compilador
/volatile:iso
puede usarse para restaurar el comportamiento consistente con el estándar, que es la configuración predeterminada cuando la plataforma objetivo es ARM.
Véase también
|
Documentación de C
para
memory order
|
Enlaces externos
| 1. | Protocolo MOESI |
| 2. | x86-TSO: Un Modelo de Programador Riguroso y Utilizable para Multiprocesadores x86 P. Sewell et. al., 2010 |
| 3. | Introducción Tutorial a los Modelos de Memoria Relajada ARM y POWER P. Sewell et al, 2012 |
| 4. | MESIF: Un Protocolo de Coherencia de Caché de Dos Saltos para Interconexiones Punto a Punto J.R. Goodman, H.H.J. Hum, 2009 |
| 5. | Modelos de Memoria Russ Cox, 2021 |
|
Esta sección está incompleta
Razón: Busquemos buenas referencias sobre QPI, MOESI y quizás Dragon. |