usar tutorial thread libreria library how ejemplos ejemplo c++ multithreading concurrency c++11 parallel-processing

tutorial - Concurrencia: atómico y volátil en el modelo de memoria C++ 11



thread library c++ (4)

Una variable global se comparte en 2 subprocesos simultáneos en 2 núcleos diferentes. Los hilos escriben y leen de las variables. Para la variable atómica, ¿puede un hilo leer un valor obsoleto? Cada núcleo puede tener un valor de la variable compartida en su caché y cuando uno enhebra escrituras a su copia en un caché, el otro subproceso en un núcleo diferente puede leer el valor obsoleto de su propio caché. ¿O el compilador hace una fuerte ordenación de la memoria para leer el último valor de la otra caché? La biblioteca estándar de c ++ 11 tiene std :: atomic support. ¿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?


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. Busque la palabra clave "registrarse" anterior en C. "Volatile" es básicamente el operador "-" para "registrar" ''s "+". Los compiladores modernos ahora hacen la optimización que "registra" solía solicitar explícitamente de manera predeterminada, por lo que ya solo ves "volátil". Usar el calificador volátil garantizará que su procesamiento nunca use un valor obsoleto, pero nada más.

2) Atómico:
Las operaciones atómicas modifican datos en un solo tic del reloj, de modo 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 admita el hardware; cosas como ++, - e intercambiando 2 punteros. Tenga en cuenta que esto no dice nada sobre el ORDEN los diferentes hilos ejecutarán las instrucciones atómicas, solo que nunca se ejecutarán en paralelo. Es por eso que tiene todas esas opciones adicionales para forzar un pedido.


Volatile y Atomic tienen diferentes propósitos.

Volátil: informa al compilador para evitar la optimización. Esta palabra clave se usa para las variables que deben cambiar inesperadamente. Por lo tanto, se puede usar para representar los registros de estado del hardware, las variables de ISR, las variables compartidas en una aplicación de subprocesos múltiples.

Atómico: también se usa en caso de aplicación de subprocesos múltiples. Sin embargo, esto garantiza que no haya bloqueo / bloqueo mientras se usa en una aplicación de subprocesos múltiples. Las operaciones atómicas son libres de razas e indivisibles. Pocos del escenario clave de uso es comprobar si un bloqueo es libre o usado, agregar atómicamente al valor y devolver el valor agregado, etc. en una aplicación de subprocesos múltiples.


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

fechas volatile desde hace mucho tiempo, y está principalmente diseñado para evitar optimizaciones del compilador cuando se accede a IO asignada a la memoria. Los compiladores modernos tienden a no hacer más que suprimir las optimizaciones de volatile , aunque en algunas máquinas, esto no es suficiente ni siquiera para la IO asignada a la memoria. Excepto por el caso especial de manejadores de señal, y las secuencias setjmp , longjmp y getjmp (donde el estándar C, y en el caso de las señales, el estándar Posix, ofrece garantías adicionales), debe considerarse inútil en una máquina moderna, donde sin instrucciones especiales adicionales (vallas o barreras de memoria), el hardware puede reordenar o incluso suprimir ciertos accesos. Como no deberías estar usando setjmp et al. en C ++, esto deja más o menos a los manejadores de señal, y en un entorno multiproceso, al menos en Unix, existen mejores soluciones para esos también. Y posiblemente IO asignado a la memoria, si está trabajando en el código kernal y puede garantizar que el compilador genere lo que sea necesario para la plataforma en cuestión. (Según el estándar, el acceso volatile es un comportamiento observable que el compilador debe respetar. Pero el compilador define lo que se entiende por "acceso", y la mayoría parece definirlo como "se ejecutó una instrucción load or store machine". Lo cual, en un procesador moderno, ni siquiera significa que haya necesariamente un ciclo de lectura o escritura en el bus, y mucho menos que esté en el orden esperado).

Dada esta situación, el estándar de C ++ agregó acceso atómico, que proporciona un cierto número de garantías entre los hilos; en particular, el código generado en torno a un acceso atómico contendrá las instrucciones adicionales necesarias para evitar que el hardware vuelva a ordenar los accesos, y para garantizar que los accesos se propaguen a la memoria global compartida entre los núcleos en una máquina multinúcleo. (En un punto del esfuerzo de estandarización, Microsoft propuso agregar esta semántica a volatile , y creo que algunos de sus compiladores de C ++ sí lo hacen. Sin embargo, después del debate de los problemas en el comité, el consenso general -incluido el representante de Microsoft- fue que era mejor dejar la volatile con su significado original y definir los tipos atómicos.) O simplemente usar las primitivas de nivel del sistema, como mutexes, que ejecutan las instrucciones necesarias en su código. (Tienen que hacerlo. No puede implementar un mutex sin algunas garantías sobre el orden de los accesos de memoria).


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 , ya menos que su plataforma documente lo contrario, volatile no tiene relación con el acceso atómico o la ordenación de la memoria entre subprocesos.

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

std::atomic<int> ai;

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

En ausencia de cualquier sincronización adicional, si un hilo escribe un valor en ai entonces no hay nada que garantice que otro hilo verá el valor en un período de tiempo dado. La norma 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 el lugar en que se encuentra su operación en este orden total.

Si tiene 2 variables compartidas x e y , inicialmente cero, y tiene un hilo escriba 1 en x y otro escriba 2 en y , entonces un tercer hilo que lea ambos puede ver cualquiera (0,0), (1,0), (0,2) o (1,2) ya que no existe una restricción de ordenación 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, lo 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 el escribir antes en x es visible. Los otros 3 emparejamientos (0,0), (1,0) y (1,2) siguen siendo posibles, dependiendo de cómo las 2 lecturas se entrelazan con las 2 escrituras.

Si utiliza otras ordenaciones de memoria como std::memory_order_relaxed o std::memory_order_acquire , las restricciones se relajan aún más, y el único pedido global ya no se aplica. Los hilos ni siquiera necesariamente tienen que acordar el pedido de dos tiendas para separar las variables si no hay una sincronización adicional.

La única forma de garantizar que tiene el "último" valor es usar una operación de lectura, modificación y escritura, como exchange() , compare_exchange_strong() o fetch_add() . Las operaciones de lectura-modificación-escritura tienen una restricción adicional de que siempre operan con el valor "más reciente", por lo que una secuencia de ai.fetch_add(1) por una serie de hilos devolverá una secuencia de valores sin duplicados o brechas. 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 un montón de material de referencia y examine el código publicado antes de escribir el código de producción con átomos. En la mayoría de los casos, es más fácil escribir código que usa bloqueos y no notablemente menos eficiente.