Concurrencia: Atómica y volátil en el modelo de memoria C++11


Una variable global se comparte a través de 2 subprocesos que se ejecutan simultáneamente en 2 núcleos diferentes. Los hilos escriben y leen de las variables. Para la variable atómica puede un hilo leer un valor rancio? Cada núcleo puede tener un valor de la variable compartida en su caché y cuando un subproceso escribe en su copia en una caché, el otro subproceso en un núcleo diferente puede leer un valor obsoleto de su propia caché. ¿O el compilador hace un orden de memoria fuerte para leer el último valor de la otra caché? El la biblioteca estándar c++11 tiene soporte std:: atomic. ¿En qué se diferencia esto de la palabra clave volátil? ¿Cómo los tipos volátiles y atómicos se comportarán de manera diferente en el escenario anterior?

Author: Abhijit Kadam, 2012-01-11

4 answers

En primer lugar, volatile no implica acceso atómico. Está diseñado para cosas como E/S mapeadas en memoria y manejo de señales. volatile es completamente innecesario cuando se usa con std::atomic, y a menos que su plataforma documente lo contrario, volatile no tiene relación con el acceso atómico o el ordenamiento de memoria entre subprocesos.

Si tiene una variable global que se comparte entre subprocesos, como:

std::atomic<int> ai;

Entonces las restricciones de visibilidad y orden dependen del parámetro de orden de memoria que utilice para las operaciones, y los efectos de sincronización de bloqueos, hilos y accesos a otras variables atómicas.

En ausencia de sincronización adicional, si un subproceso escribe un valor a ai entonces no hay nada que garantice que otro subproceso verá el valor en un período de tiempo dado. El estándar especifica que debe ser visible "en un período de tiempo razonable", pero cualquier acceso dado puede devolver un valor obsoleto.

El orden de memoria predeterminado de std::memory_order_seq_cst proporciona un único orden total global para todas las operaciones std::memory_order_seq_cst en todas las variables. Esto no significa que no pueda obtener valores obsoletos, pero sí significa que el valor que obtiene determina y está determinado por dónde se encuentra su operación en este orden total.

Si tienes 2 variables compartidas x y y, inicialmente cero, y tienes un subproceso que escribe 1 a x y otro que escribe 2 a y, entonces un tercer subproceso que lee ambos puede ver (0,0), (1,0), (0,2) o (1,2) ya que no hay restricción de orden entre las operaciones, y por lo tanto las operaciones pueden aparecer en cualquier orden en el orden global.

Si ambas escrituras son del mismo hilo, que hace x=1 antes de y=2 y el hilo de lectura lee y antes de x entonces (0,2) ya no es una opción válida, ya que la lectura de y==2 implica que la escritura anterior a x es visible. Los otros 3 emparejamientos (0,0), (1,0) y (1,2) todavía son posibles, dependiendo de cómo el 2 lee interleave con el 2 escribir.

Si utiliza otros ordenamientos de memoria como std::memory_order_relaxed o std::memory_order_acquire entonces las restricciones se relajan aún más, y el orden global único ya no se aplica. Los hilos ni siquiera necesariamente tienen que ponerse de acuerdo en el orden de dos almacenes para separar variables si no hay sincronización adicional.

La única manera de garantizar que tiene el valor" último " es usar una operación de lectura-modificación-escritura como exchange(), compare_exchange_strong() o fetch_add(). Las operaciones de lectura-modificación-escritura tienen restricción adicional de que siempre operan en el valor" último", por lo que una secuencia de operaciones ai.fetch_add(1) por una serie de subprocesos devolverá una secuencia de valores sin duplicados o huecos. Sin embargo, en ausencia de restricciones adicionales, todavía no hay garantía de qué hilos verán qué valores.

Trabajar con operaciones atómicas es un tema complejo. Le sugiero que lea mucho material de fondo y examine el código publicado antes de escribir código de producción con atomics. En la mayoría casos es más fácil escribir código que utiliza bloqueos, y no notablemente menos eficiente.

 68
Author: Anthony Williams,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-01-12 10:05:52

volatile y las operaciones atómicas tienen un fondo diferente, y se introdujeron con una intención diferente.

volatile data de hace mucho tiempo, y está diseñado principalmente para prevenir optimizaciones del compilador al acceder a E / s asignadas a memoria. Modernista los compiladores tienden a no hacer más que suprimir optimizaciones para volatile, aunque en algunas máquinas, esto no es suficiente incluso para la memoria mapeada IO. Excepto en el caso especial de los manipuladores de señales, y setjmp, longjmp y getjmp secuencias (donde el C estándar, y en el caso de señales, el estándar Posix, da garantías adicionales), debe ser considerado inútil en una máquina moderna, donde sin especial adicional instrucciones (cercas o barreras de memoria), el hardware puede reordenar o incluso suprimir ciertos accesos. Ya que no deberías usar setjmp et al. en C++, esto más o menos deja manejadores de señal, y en un entorno multiproceso, al menos bajo Unix, hay mejores soluciones para ellos también. Y posiblemente memoria lo mapeado, si eres trabajando en código kernal y puede asegurar que el compilador genere lo que sea necesario para la plataforma en cuestión. (De acuerdo con la estándar, volatile el acceso es un comportamiento observable, que el compilador debe respetar. Pero el compilador llega a definir lo que se entiende por "acceso", y la mayoría parece definirlo como " una carga o se ejecutó la instrucción de la máquina de almacenamiento". Que, en un moderno procesador, ni siquiera significa que hay necesariamente una lectura o escritura ciclo en el autobús, mucho menos que está en el orden que esperas.)

Dada esta situación, el estándar C++ agregó acceso atómico, que hace proporcionar un cierto número de garantías a través de hilos; en particular, el código generado en torno a un acceso atómico contendrá lo necesario instrucciones adicionales para evitar que el hardware reordene el acceso, y para asegurar que los accesos se propagan hacia el memoria compartida entre núcleos en una máquina multinúcleo. (En un momento en el esfuerzo de normalización, Microsoft propuso agregar estas semánticas a volatile, y creo que algunos de sus compiladores de C++ lo hacen. Despues examen de las cuestiones en el comité, sin embargo, la consenso-incluyendo el representante de Microsoft-fue que era mejor dejar volatile con su significado original, y definir los tipos atómicos.) O simplemente use las primitivas de nivel de sistema, como mutexes, que ejecutan las instrucciones necesarias en su código. (Tienen que hacerlo. No se puede implementar un mutex sin algunos garantizar sobre el orden de los accesos a la memoria.)

 27
Author: James Kanze,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-01-11 13:10:13

Volátil y Atómico sirven diferentes propósitos.

Volátil : Informa al compilador para evitar la optimización. Esta palabra clave se usa para variables que cambiarán inesperadamente. Por lo tanto, se puede utilizar para representar los registros de estado de hardware, variables de ISR, Variables compartidas en una aplicación multihilo.

Atómico : También se utiliza en caso de aplicación multihilo. Sin embargo, esto asegura que no haya bloqueo/pérdida mientras se usa en una aplicación multihilo. Atómico las operaciones están libres de razas e indivisibles. Pocos de los escenarios clave de uso es comprobar si un bloqueo es libre o utilizado, agregar atómicamente al valor y devolver el valor agregado, etc. en aplicación multihilo.

 3
Author: Karthik Balaguru,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-10-24 19:08:07

Aquí hay una sinopsis básica de lo que son las 2 cosas:

1) Palabra clave volátil:
Le dice al compilador que este valor podría alterarse en cualquier momento y por lo tanto nunca debería almacenarlo en caché en un registro. Busca la vieja palabra clave " register "en C." Volatile "es básicamente el operador" - "para"register "'s"+". Los compiladores modernos ahora hacen la optimización que" register " solía solicitar explícitamente por defecto, por lo que ya solo se ve 'volátil'. El uso del calificador volátil garantizará que su procesamiento nunca usa un valor obsoleto, pero nada más.

2) Atómico:
Las operaciones atómicas modifican los datos en un solo tick de reloj, por lo que es imposible que cualquier otro hilo acceda a los datos en medio de dicha actualización. Por lo general, se limitan a las instrucciones de ensamblaje de un solo reloj que admite el hardware; cosas como++, -- e intercambiar 2 punteros. Tenga en cuenta que esto no dice nada sobre el ORDEN en que los diferentes hilos ejecutarán las instrucciones atómicas, solo que nunca corras en paralelo. Es por eso que tiene todas esas opciones adicionales para forzar un pedido.

 2
Author: Zack Yezek,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-08-02 02:20:30