2010-03-19 11 views
17

Quiero verificar que mi comprensión es correcta. Este tipo de cosas es complicado, así que estoy casi seguro de que me falta algo. Tengo un programa que consiste en un hilo en tiempo real y un hilo no en tiempo real. Quiero que el subproceso que no sea RT pueda intercambiar un puntero a la memoria que usa el subproceso RT.Intercambio atómico en GNU C++

A partir de los documentos, mi entendimiento es que esto se puede lograr en g++ con:

// global 
Data *rt_data; 

Data *swap_data(Data *new_data) 
{ 
#ifdef __GNUC__ 
    // Atomic pointer swap. 
    Data *old_d = __sync_lock_test_and_set(&rt_data, new_data); 
#else 
    // Non-atomic, cross your fingers.           
    Data *old_d = rt_data; 
    rt_data = new_data; 
#endif 
    return old_d; 
} 

Este es el único lugar en el programa (que no sea la configuración inicial), donde se modifica rt_data. Cuando se usa rt_data en el contexto en tiempo real, se copia a un puntero local. Para old_d, más adelante cuando esté seguro de que no se usa la memoria anterior, se liberará en la secuencia que no sea RT. ¿Es esto correcto? ¿Necesito volatile en algún lado? ¿Hay otras primitivas de sincronización que debería llamar?

Por cierto que estoy haciendo esto en C++, aunque estoy interesado en saber si la respuesta es diferente para C

Gracias de antemano.

Respuesta

24

Generalmente no use volatile al escribir código concurrente en C/C++.La semántica de volatile está tan cerca de lo que quiere que sea tentador, pero al final volátil es not enough. Desafortunadamente Java/C# volatile != C/C++ volatile. Herb Sutter tiene un gran article que explica el confuso desorden.

Lo que realmente quieres es una valla de memoria. __sync_lock_test_and_set proporciona la esgrima para usted.

También necesitará una cerca de memoria cuando copie (cargue) el puntero rt_data a su copia local.

La programación sin bloqueos es complicada. Si usted está dispuesto a utilizar C++ 0x extensiones de GCC, que es un poco más fácil:

#include <cstdatomic> 

std::atomic<Data*> rt_data; 

Data* swap_data(Data* new_data) 
{ 
    Data* old_data = rt_data.exchange(new_data); 
    assert(old_data != new_data); 
    return old_data; 
} 

void use_data() 
{ 
    Data* local = rt_data.load(); 
    /* ... */ 
} 
+0

¡Gracias! Puedo seguir tu sugerencia de usar std :: atomic, eso es excelente. (Todavía no estoy familiarizado con las últimas novedades de C++ 0x). Solo por curiosidad, si uso __sync_lock_test_and_set, ¿cuál es la valla correcta para usar mientras leo? (es decir, para hacer una copia local) – Steve

3

actualización: Esta respuesta no es correcta, ya que me falta el hecho de que volatile garantiza que los accesos a volatile variables no son reordenados, pero no proporciona ningún tipo de garantías con respecto a otros accesos no volatile y manipulaciones. Una valla de memoria proporciona tales garantías, y es necesaria para esta aplicación. Mi respuesta original está abajo, pero no actúes en consecuencia. Ver this answer para una buena explicación en el hoyo en mi entender que condujo a la siguiente respuesta incorrecta.

Respuesta original:

Sí, necesita volatile en su declaración rt_data; cada vez que una variable se puede modificar fuera del flujo de control de un hilo que accede a ella, debe declararse volatile. Si bien es posible que pueda alejarse sin volatile ya que está copiando a un puntero local, volatile al menos ayuda con la documentación y también inhibe algunas optimizaciones del compilador que pueden causar problemas. Considere el ejemplo siguiente, adoptado de DDJ:

volatile int a; 
int b; 
a = 1; 
b = a; 

Si es posible que a a tienen su valor cambió entre a=1 y b=a, entonces a debe declararse volatile (a menos que, por supuesto, la asignación de un fuera de la el valor de fecha a b es aceptable). El subprocesamiento múltiple, particularmente con primitivas atómicas, constituye tal situación. La situación también se desencadena con variables modificadas por controladores de señal y por variables asignadas a ubicaciones de memoria impares (por ejemplo, registros de E/S de hardware). Consulte también this question.

De lo contrario, me parece bien.

En C, probablemente usaría las primitivas atómicas proporcionadas por GLib para esto. Usarán una operación atómica cuando esté disponible y recurrirán a una implementación basada en mutex lenta pero correcta si las operaciones atómicas no están disponibles. Boost puede proporcionar algo similar para C++.

+5

volátil tiene nada que ver con la concurrencia, que son completamente ortogonales. Ambos se ocupan de forzar la carga/almacenamiento y el reordenamiento, pero las garantías proporcionadas por la volatilidad no resuelven los problemas concurrentes. –

+0

@Caspin Sí, volátil es ortogonal a los problemas de concurrencia, y volátil solo no es suficiente. Sin embargo, entiendo que volátil es útil en la programación simultánea para garantizar que los hilos vean los cambios entre ellos. No hay mucha diferencia entre que una variable sea cambiada por otra cadena y que sea modificada por una interrupción de hardware; ambas violan las suposiciones necesarias para el reordenamiento de carga/tienda, y volátil le dice al compilador que esas suposiciones no necesariamente son válidas. –

+0

Ortogonal significa que los problemas de soluciones volátiles no son el tipo de problemas que crea la programación simultánea. Uno en particular, la garantía de pedido volátil solo se aplica al hilo actual y solo con respecto a otros volátiles. Por favor, lea los enlaces provistos en mi respuesta, ya que la descripción completa de volátil es demasiado compleja para caber en un comentario. O mejor aún pregunte a stackoverflow "¿Por qué no es volátil útil para la programación concurrente c/C++?" y proporcionaré una respuesta en profundidad. –