Cuándo usar volátil con multi threading?

Si hay dos subprocesos que acceden a una variable global, muchos tutoriales dicen que la variable es volátil para evitar que el comstackdor guarde en caché la variable en un registro y, por lo tanto, no se actualice correctamente. Sin embargo, dos subprocesos que acceden a una variable compartida son algo que requiere protección a través de un mutex, ¿no? Pero en ese caso, entre el locking del hilo y la liberación del mutex, el código está en una sección crítica donde solo ese hilo puede acceder a la variable, en cuyo caso la variable no necesita ser volátil.

Entonces, ¿cuál es el uso / propósito de volátil en un progtwig multiproceso?

Respuesta breve y rápida : volatile es (casi) inútil para la progtwigción de aplicaciones multiproceso y agnóstica de plataforma. No proporciona ninguna sincronización, no crea vallas de memoria, ni garantiza el orden de ejecución de las operaciones. No hace que las operaciones sean atómicas. No hace que tu código sea mágicamente seguro. volatile puede ser la instalación más incomprendida en todo C ++. Ver esto , esto y esto para obtener más información sobre volatile

Por otro lado, volatile tiene algún uso que puede no ser tan obvio. Se puede usar de la misma manera que uno usaría const para ayudar al comstackdor a mostrarle dónde podría estar cometiendo un error al acceder a algún recurso compartido de una manera no protegida. Este uso es discutido por Alexandrescu en este artículo . Sin embargo, esto es básicamente el uso del sistema de tipo C ++ de una manera que a menudo se ve como una invención y puede evocar un comportamiento indefinido.

volatile fue diseñado específicamente para ser utilizado cuando se hace interfaz con hardware mapeado en memoria, manejadores de señal y la instrucción de código de máquina setjmp. Esto hace que los volatile sean directamente aplicables a la progtwigción a nivel de sistemas en lugar de la progtwigción a nivel de aplicaciones normales.

El estándar C ++ de 2003 no dice que volatile aplica ningún tipo de semántica de adquisición o liberación en las variables. De hecho, el estándar es completamente silencioso en todos los asuntos de multihilo. Sin embargo, las plataformas específicas aplican la semántica de Adquisición y Liberación en variables volatile .

[Actualización para C ++ 11]

El estándar C ++ 11 ahora reconoce el subprocesamiento múltiple directamente en el modelo de memoria y en el idioma, y ​​proporciona servicios de biblioteca para manejarlo de manera independiente de la plataforma. Sin embargo, la semántica de volatile todavía no ha cambiado. volatile aún no es un mecanismo de sincronización. Bjarne Stroustrup lo dice en TCPPPL4E:

No use volatile excepto en el código de bajo nivel que trata directamente con el hardware.

No asum que volatile tiene un significado especial en el modelo de memoria. No es asi. No es, como en algunos idiomas posteriores, un mecanismo de sincronización. Para obtener la sincronización, use atomic , mutex o condition_variable .

[/ Fin actualización]

Todo lo anterior aplica el lenguaje C ++ en sí mismo, tal como lo define el Estándar 2003 (y ahora el Estándar 2011). Sin embargo, algunas plataformas específicas agregan funcionalidad o restricciones adicionales a lo que hace volatile . Por ejemplo, en MSVC 2010 (al menos) la semántica de Adquirir y Liberar se aplica a ciertas operaciones en variables volatile . Desde MSDN :

Al optimizar, el comstackdor debe mantener el orden entre las referencias a los objetos volátiles, así como las referencias a otros objetos globales. En particular,

Una escritura en un objeto volátil (escritura volátil) tiene semántica de lanzamiento; una referencia a un objeto global o estático que ocurre antes de que se escriba una escritura en un objeto volátil en la secuencia de instrucciones antes de esa escritura volátil en el binario comstackdo.

Una lectura de un objeto volátil (lectura volátil) tiene Semántica de adquisición; una referencia a un objeto global o estático que se produce después de una lectura de memoria volátil en la secuencia de instrucciones se producirá después de esa lectura volátil en el binario comstackdo.

Sin embargo, puede tomar nota del hecho de que si sigue el enlace anterior, existe cierto debate en los comentarios acerca de si realmente se aplica la semántica de adquisición / liberación en este caso.

Volatile ocasionalmente es útil por el siguiente motivo: este código:

 /* global */ bool flag = false; while (!flag) {} 

está optimizado por gcc para:

 if (!flag) { while (true) {} } 

Lo cual es obviamente incorrecto si el otro hilo escribe la bandera. Tenga en cuenta que sin esta optimización, el mecanismo de sincronización probablemente funcione (dependiendo del otro código pueden necesitarse algunas barreras de memoria) – no es necesario un mutex en 1 productor – 1 escenario de consumidor.

De lo contrario, la palabra clave volátil es demasiado extraña para ser utilizable: no proporciona garantías de ordenación de memoria con accesos volátiles y no volátiles y no proporciona ninguna operación atómica, es decir, no obtiene ayuda del comstackdor con palabra clave volátil excepto el almacenamiento en caché de registro deshabilitado. .

Necesitas volátiles y posiblemente locking.

volátil le dice al optimizador que el valor puede cambiar de forma asíncrona, por lo tanto

 volatile bool flag = false; while (!flag) { /*do something*/ } 

leerá la bandera cada vez que esté en el ciclo.

Si desactiva la optimización o hace que cada variable sea volátil, un progtwig se comportará igual pero más lento. volátil solo significa ‘Sé que acabas de leerlo y saber lo que dice, pero si digo leerlo, léelo.

Locking es parte del progtwig. Así que, por cierto, si está implementando semáforos, entonces, entre otras cosas, debe ser volátil. (No lo intentes, es difícil, probablemente necesites un pequeño ensamblador o las nuevas cosas atómicas, y ya está hecho).

 #include  #include  #include  using namespace std; bool checkValue = false; int main() { std::thread writer([&](){ sleep(2); checkValue = true; std::cout < < "Value of checkValue set to " << checkValue << std::endl; }); std::thread reader([&](){ while(!checkValue); }); writer.join(); reader.join(); } 

Una vez que un entrevistador que también creía que la volatilidad es inútil, argumentó conmigo que la optimización no causaría ningún problema y se refería a diferentes núcleos que tenían líneas de caché separadas y todo eso (realmente no entendía a qué se refería exactamente). Pero este fragmento de código cuando se comstack con -O3 en g ++ (g ++ -O3 thread.cpp -lpthread), muestra un comportamiento indefinido. Básicamente, si el valor se establece antes de la comprobación while funciona bien y si no entra en un bucle sin molestarse en recuperar el valor (que en realidad cambió con el otro hilo). Básicamente, creo que el valor de checkValue solo se obtiene una vez en el registro y nunca se vuelve a verificar en el nivel más alto de optimización. Si se establece en verdadero antes de la búsqueda, funciona bien y si no entra en un bucle. Por favor corrígeme si estoy equivocado.