2010-09-06 11 views
5

Tengo una aplicación C++ de subprocesos múltiples.palabra clave volátil C++ con variable compartida global accedida por la función

Ahora sé que para variables compartidas globales, debe usar volátil en algunos casos mientras comprueba el estado de la variable o el compilador podría suponer que el valor de la variable nunca cambia (en esa cadena).

¿Qué sucede si, sin embargo, en lugar de verificar el estado de una variable, llamo a un método que devuelve el valor de la variable? Por ejemplo:

static int num = 0; 

... 

void foo() 
{ 
    while(getNum() == 0) 
    { 
     // do something (or nothing) 
    } 
} 

¿Todavía tiene que hacer num una variable volátil? ¿O el compilador reconoce que, dado que estoy usando un método para acceder a esa variable num, no almacenará en caché el resultado?

¿Alguien tiene alguna idea?

Gracias de antemano,

~ Julian

edición: dentro de mi bucle while Quité la llamada del sueño y lo reemplazó con algo genérico como un comentario que hacer algo (o nada)

+0

Marque esta pregunta (http://stackoverflow.com/questions/3148319/is-volatile-required-for-shared-memory-accessed-via-access-function). – gablin

Respuesta

4

No, volatile nunca es necesario, siempre que esté realizando la sincronización necesaria.

Las funciones de sincronización de la biblioteca de hilos de llamada, independientemente de lo que sean en su plataforma, deberían ocuparse de invalidar los valores "en caché" locales y hacer que el compilador vuelva a cargar los valores globales.

En este caso particular, es probable que sleep tenga tal efecto, pero de todos modos no es una buena implementación. Debe haber una variable de condición en num, protéjala con una función setter y haz que la función setter envíe una señal al foo.

En cuanto a la pregunta específica, si la función oculta el acceso de la optimización es extremadamente dependiente de la implementación y de la situación. Su mejor opción es compilar la función getter en una invocación separada del compilador, pero incluso así, no hay forma de garantizar que la optimización interprocedural no ocurra. Por ejemplo, algunas plataformas pueden poner código IR en los archivos .o y realizar la generación de código en la etapa "enlazador".

Descargo de responsabilidad.

palabras clave anteriores: 1. todo el tiempo que está haciendo la sincronización necesaria y 2. probable que tenga un efecto tan.

1: sleep o un busy-loop vacío no son "sincronización necesaria". Esa no es la forma correcta de escribir un programa multiproceso, punto. Por lo tanto, puede ser necesario volátil en tales casos.

2: Sí, sleep puede no contabilizarse mediante la implementación de una función de E/S, e incluso puede estar etiquetada como pura y sin efectos secundarios. En ese caso, sería necesario volatile en el mundo. Sin embargo, dudo que se hayan distribuido realmente las implementaciones, lo que rompería los bucles sleep, ya que lamentablemente son comunes.

+5

+1: 'volátil' es * no * un sustituto de las construcciones de sincronización de subprocesamiento adecuado. –

+0

"Llamar a las funciones de sincronización de la biblioteca de hilos debería ocuparse de invalidar los valores locales 'en caché' y hacer que el compilador recargue globales". Esto es completamente incorrecto o correcto de una manera sutil que necesita una explicación adicional ... Mi entendimiento es que está mal, llamar '' dormir' NO obligará al compilador a volver a cargar el valor de lo global. De hecho, al ser parte de las bibliotecas estándar, es posible que sepa que no tiene ningún efecto secundario y, como tal, el compilador puede deducir que no es necesario volver a leer la variable. –

+0

Lea la pregunta/respuestas vinculadas por gablin en un comentario a la pregunta. –

-1

técnicamente debe marcarse como volátil. Los compiladores son libres de hacer lo que quieran para optimizar el código, siempre y cuando este se ajuste a la especificación de la máquina abstracta C++. Un compilador conforme, con recursos suficientes, podría alinear todas las instancias de getNum, mover el valor de num en un registro (o simplemente, darse cuenta de que en realidad nunca cambia por ningún código, tratarlo como una constante) durante toda la vida del programa .

En términos prácticos, ninguna CPU (actual) tiene suficientes registros libres que incluso el compilador optimizador más agresivo elegiría para hacer eso. Pero si se desea la corrección, se requiere volatilidad.

+0

Bien, pero tomemos una situación ligeramente diferente. Digamos que el * num * estático ahora se convierte en una variable miembro privada accesible a través de getNum y, como resultado, el compilador no pudo acceder en línea. ¿Todavía tendría que marcar * num * como volátil? ... Creo que la respuesta correcta en ambos casos (la original y la nueva) es que necesito introducir la sincronización a través de bloqueos. – jbu

+0

Creo que la mayoría de los compiladores son lo suficientemente inteligentes como para darse cuenta de que una variable global en un programa multiproceso puede ser cambiada por otro hilo. –

+0

No importa cuán enrevesado lo hagas. La máquina abstracta C++ no reconoce los hilos. Por lo tanto, una variable que se puede cambiar por un hilo diferente se está cambiando fuera de la máquina abstracta actual. Por lo tanto, para ser correcto, NECESITA etiquetarse como volátil. –

2

Lo que usted propone es básicamente un mal uso de "volátil", su verdadero propósito es decirle al compilador que la variable puede ser modificada por hardware externo u otro proceso en el sistema, por lo tanto, necesita ser realmente leído de memoria cada vez que se usa.

No lo protegerá de colisiones de hilos, etc. dentro de su programa, aunque en su caso parece que está utilizando la bandera para señalar una solicitud de cierre.

Está bien hacerlo sin sincronizar, siempre que sepa que solo un único hilo de control actualizará la variable. También usaría un poco de manipulación para establecer la bandera ya que es más probable que sea "atómica" en más hardware.

num && x'00000001' 
+0

Sí, creo que debería usar mecanismos de sincronización para estar seguro y correcto. – jbu

0

Desafortunadamente la semántica volátil es un poco desagradable. El concepto de volátil no estaba realmente destinado a ser utilizado para enhebrar.

Potatoswatter es correcto que llamar a las primitivas de sincronización del sistema operativo normalmente evitará que el compilador de optimización eleve la lectura de num del bucle. Pero funciona por la misma razón por la que el uso de un método de acceso funciona ... por accidente.

El compilador lo ve llamando a una función que no está inmediatamente disponible para alineación o análisis, por lo que tiene que suponer que cualquier variable que podría ser utilizada por alguna otra función podría leerse o alterarse en esa función opaca. Entonces, antes de hacer la llamada, el compilador necesita volver a escribir en la memoria todas esas variables "globales".

En corensic, agregamos una función en línea a jinx.h que hace esto de una manera más directa. Algo así como lo siguiente:

inline void memory_barrier() { asm volatile("nop" ::: "memory"); } 

Esto es bastante sutil, pero efectivamente le dice al compilador (gcc) que no puede deshacerse de este trozo de asm opaca y que el asm opaca puede leer o escribir cualquier nivel mundial pedazo de memoria visible. Esto efectivamente detiene el compilador de reordenar cargas/tiendas a través de este límite.

Para su ejemplo:

memory_barrier(); while (num == 0) { memory_barrier(); ... }

Ahora la lectura de num está pegada en su lugar. Y potencialmente más importante, está atascado en su lugar con relación a otro código. Por lo que podría tener:

while (flag == 0) { memory_barrier(); } // spin 
process data[0..N] 

Y otro hilo hace:

populate data[0..N] 
memory_barrier(); 
flag = 1; 

PS.Si haces este tipo de cosas (esencialmente creando tus propias primitivas de sincronización), las ganancias de rendimiento pueden ser grandes pero el riesgo de calidad es alto. Jinx es particularmente bueno para encontrar errores en estas estructuras sin cerrojo. Así que es posible que desee usarlo o alguna otra herramienta para ayudar a probar esto.

PPS. La comunidad de Linux tiene una buena publicación sobre esto llamada "volátil considerado perjudicial", compruébalo.

+0

"_Pero funciona por la misma razón por la que el uso de un método de acceso funciona ... por accidente." "Explique. – curiousguy

Cuestiones relacionadas