2008-09-22 10 views
27

Tengo una aplicación donde se están ejecutando 2 subprocesos ... ¿Hay alguna certeza de que cuando cambio una variable global de un subproceso, el otro notará este cambio? que no tienen ninguna sincronización o el sistema de exclusión mutua en lugar ... pero este trabajo debe código todo el tiempo (imaginar un mundial bool llamado dataUpdated):Subproceso C++, datos compartidos

Tema 1:

while(1) { 
    if (dataUpdated) 
     updateScreen(); 
    doSomethingElse(); 
} 

Tema 2:

while(1) { 
    if (doSomething()) 
     dataUpdated = TRUE; 
} 

¿tiene un compilador gcc como optimizar el código de una manera que no comprueba el valor global, teniendo en cuenta que sólo se valor en tiempo de compilación (porque nunca se cambia en el mismo grupo)?

PD: Siendo esto para una aplicación similar a un juego, realmente no importa si habrá una lectura mientras se escribe el valor ... todo lo que importa es que el cambio sea notado por el otro hilo.

Respuesta

23

Sí. No, quizás.

En primer lugar, como han mencionado otros, necesita hacer volátil DataUpdate; de lo contrario, el compilador puede levantar libremente la lectura desde el bucle (dependiendo de si puede o no ver que DoSomethingElse no lo toca).

En segundo lugar, dependiendo de su procesador y las necesidades de pedido, es posible que necesite barreras de memoria. volátil es suficiente para garantizar que el otro procesador verá el cambio con el tiempo, pero no lo suficiente como para garantizar que los cambios se verán en el orden en que se realizaron. Tu ejemplo solo tiene una bandera, por lo que realmente no muestra este fenómeno. Si necesita y utiliza barreras de memoria, ya no necesitará los volátiles

Volatile considered harmful y Linux Kernel Memory Barriers son buenos antecedentes sobre los problemas subyacentes; Realmente no conozco nada similar escrito específicamente para enhebrar. Afortunadamente, los hilos no plantean estas preocupaciones casi tan a menudo como los periféricos de hardware, aunque el tipo de caso que describes (un indicador que indica que se completó, con otros datos presuntamente válidos si se establece el indicador) es exactamente el tipo de ordenamiento matterns ...

+0

Esta es una buena información adicional a considerar además de los puntos que hice en mi respuesta aquí. –

+0

"Si necesita y usa barreras de memoria, ya no debería necesitar volátiles" Um ... ¿aún no necesitaría volátiles para evitar que el compilador guarde en caché la variable en un registro para el bucle que lee el valor, por ejemplo? De la misma respuesta: "necesitas hacer que DataUpdated sea volátil; de lo contrario, el compilador puede leerlo sin problemas". No veo cómo una barrera de memoria ayuda con esto. –

+0

Las barreras de memoria deben implementarse de todos modos con la ayuda del compilador. Si le pide al compilador que se asegure de que la CPU no puede ejecutar la carga/almacenamiento exactamente en este orden, entonces el compilador debe comenzar respetando el orden en sí. Entonces la barrera implica todo lo volátil, y más. – puetzk

2

Utilice la palabra clave volátil para indicar al compilador que el valor puede cambiar en cualquier momento.

volatile int myInteger; 
+2

Esto solo funciona bajo el modelo de memoria de Java 1.5 +. El estándar de C++ no aborda el enhebrado y volátil no garantiza la coherencia de memoria entre los procesadores. Necesitas una barrera de memoria para esto. –

+0

El estándar C++ en sí mismo no define actualmente esta semántica. Pero todos los compiladores 'recientes' reconocen la sugerencia correcta como volátil. – Christopher

+1

No, no lo hacen. volátil asegura que el compilador realizará la carga como se especifica, y por lo tanto tiene en cuenta el subprocesamiento, pero no garantiza que el subsistema de memoria subyacente conservará la causalidad en un verdadero sistema multiprocesador. – puetzk

2

No, no es seguro. Si declara que la variable es volátil, se supone que el compilador genera código que siempre carga la variable de la memoria en una lectura.

3

Su solución utilizará CPU 100%, entre otros problemas. Google para "variable de condición".

+0

que si bien es solo para fines de demostración – fabiopedrosa

3

Chris Jester-Young señaló que:

Esto sólo trabajan bajo Java 1.5 Modelo + 's memoria. El estándar de C++ no aborda el enhebrado y volátil no garantiza la coherencia de memoria entre los procesadores. Necesita una barrera de memoria para este

siendo así, la única respuesta verdadera es implementar un sistema de sincronización, ¿verdad?

+0

Algunos de los comentarios insinuaron el uso de variables entrelazadas/atómicas. Está bien: usa funciones de comparación y ajuste para trabajar con ellas, y pueden ser difíciles de usar. De lo contrario, para una máxima facilidad de uso, use un candado. –

+0

de alguna manera, nadie estuvo de acuerdo porque enterraron esa respuesta y comentarios ... Gracias de todos modos: D – fabiopedrosa

+0

He votado esa respuesta porque estaba mal redactada y era confusa. Sospecho que otros hicieron lo mismo. –

7

Aquí se muestra un ejemplo que utiliza variables condición de sobrealimentación:

bool _updated=false; 
boost::mutex _access; 
boost::condition _condition; 

bool updated() 
{ 
    return _updated; 
} 

void thread1() 
{ 
    boost::mutex::scoped_lock lock(_access); 
    while (true) 
    { 
    boost::xtime xt; 
    boost::xtime_get(&xt, boost::TIME_UTC); 
    // note that the second parameter to timed_wait is a predicate function that is called - not the address of a variable to check 
    if (_condition.timed_wait(lock, &updated, xt)) 
     updateScreen(); 
    doSomethingElse(); 
    } 
} 

void thread2() 
{ 
    while(true) 
    { 
    if (doSomething()) 
     _updated=true; 
    } 
} 
1

Si el alcance es correcto ("externo", global, etc.) entonces el cambio se notará. La pregunta es cuando? ¿Y en qué orden?

El problema es que el compilador puede y con frecuencia se re-ordenar su lógica para llenar todas las tuberías es concurrentes como una optimización del rendimiento.

Realmente no se muestra en su ejemplo específico porque no hay otras instrucciones acerca de su asignación, pero imagine las funciones declaradas después de que su bool asigne ejecutar antes de la asignación.

de salida Pipeline Hazard en la wikipedia o buscar en Google de "instrucción compilador de reordenación"

7

Use un candado.Siempre use siempre un candado para acceder a los datos compartidos. Marcar la variable como volátil evitará que el compilador optimice la lectura de la memoria, pero no evitará otros problemas como memory re-ordering. Sin un bloqueo no hay garantía de que la memoria escribe en doSomething() será visible en la función updateScreen().

La única otra manera segura es usar un memory fence, explícita o implícitamente usando una función Interbloqueada * por ejemplo.

1

Como han dicho otros, la palabra clave volatile es tu amigo. :-)

Lo más probable es que descubra que su código funcionaría cuando tuviera desactivadas todas las opciones de optimización en gcc. En este caso (creo) trata todo como volátil y, como resultado, se accede a la variable en memoria para cada operación.

Con cualquier tipo de optimización activada, el compilador intentará utilizar una copia local mantenida en un registro. Dependiendo de sus funciones, esto puede significar que solo ve el cambio en la variable intermitentemente o, en el peor, nunca.

El uso de la palabra clave volatile indica al compilador que el contenido de esta variable pueden cambiar en cualquier momento y que debería no utilizar una copia en caché local.

Con todo lo dicho, puede encontrar mejores resultados (como se hace referencia en Jeff) a través del uso de un semáforo o una variable de condición.

This es una introducción razonable al tema.

6

Utilice la volátil palabra clave para hacer alusión al compilador que el valor puede cambiar en cualquier momento.

volatile int myInteger; 

Lo anterior garantizará que todo acceso a la variable será hacia y desde la memoria sin ningún tipo de optimizaciones específicas y como resultado todos los subprocesos que se ejecutan en el mismo procesador se "ver" los cambios en la variable con la misma semántica como el código lee.

Chris Jester-Young señaló que los problemas de coherencia a tal cambio de valor variable pueden surgir en un sistema multiprocesador.Esto es una consideración y depende de la plataforma.

En realidad, hay realmente dos consideraciones para pensar en relación con la plataforma. Son coherencia y atomicidad de las transacciones de memoria.

La atomicidad es en realidad una consideración tanto para las plataformas de procesador único como multiprocesador. El problema surge porque la variable es probablemente de varios bytes en la naturaleza y la pregunta es si un hilo podría ver una actualización parcial del valor o no. es decir: algunos bytes cambiados, cambio de contexto, valor no válido leído al interrumpir el hilo. Para una sola variable que está en el tamaño de la palabra de la máquina natural o más pequeño y naturalmente alineado no debería ser una preocupación. Específicamente, un tipo int siempre debe estar bien en este aspecto siempre que esté alineado, que debería ser el caso predeterminado para el compilador.

En relación con la coherencia, esta es una preocupación potencial en un sistema multiprocesador. La pregunta es si el sistema implementa la coherencia total del caché o no entre los procesadores. Si se implementa, esto generalmente se hace con el protocolo MESI en hardware. La pregunta no indicaba plataformas, pero tanto las plataformas Intel x86 como las plataformas PowerPC son coherentes entre los procesadores para regiones de datos de programas normalmente mapeados. Por lo tanto, este tipo de problema no debería ser una preocupación para los accesos de memoria de datos ordinarios entre subprocesos, incluso si hay múltiples procesadores.

El último problema relacionado con la atomicidad que surge es específico de la atomicidad de lectura-modificación-escritura. Es decir, cómo se garantiza que si se lee un valor actualizado en el valor y el escrito, que esto ocurra atómicamente, incluso en todos los procesadores si hay más de uno. Por lo tanto, para que esto funcione sin objetos de sincronización específicos, se requeriría que todos los subprocesos potenciales que acceden a la variable sean lectores ÚNICAMENTE pero se espera que solo un hilo pueda ser escritor al mismo tiempo. Si este no es el caso, entonces necesita un objeto de sincronización disponible para poder garantizar acciones atómicas en las acciones de lectura, modificación y escritura de la variable.

Cuestiones relacionadas