Intercambio atómico en GNU C++
c++20 (2)
En general, no se usa volatile
cuando se escribe código concurrente en C/C++
. La semántica de la volatile
está tan cerca de lo que quieres que es tentador, pero al final la volatilidad no es suficiente . Desafortunadamente Java/C# volatile != C/C++ volatile
. Herb Sutter tiene un gran article explica el lío confuso.
Lo que realmente quieres es una valla de memoria. __sync_lock_test_and_set
proporciona la esgrima para ti.
También necesitará una valla de memoria cuando copie (cargue) el puntero rt_data a su copia local.
Bloquear la programación libre es complicado. Si estás dispuesto a usar las extensiones c ++ 0x de Gcc, 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();
/* ... */
}
Quiero verificar que mi entendimiento es correcto. Este tipo de cosas es difícil, así que estoy casi seguro de que me estoy perdiendo algo. Tengo un programa que consiste en un subproceso en tiempo real y un subproceso no en tiempo real. Quiero que el subproceso no RT pueda intercambiar un puntero a la memoria que utiliza el subproceso RT.
De los documentos, entiendo 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 utiliza rt_data
en el contexto en tiempo real, se copia en un puntero local. Para old_d
, más adelante, cuando esté seguro de que la memoria antigua no se utiliza, se liberará en el subproceso no RT. ¿Es esto correcto? ¿Necesito volatile
lugar? ¿Hay otras primitivas de sincronización que debería llamar?
Por cierto, estoy haciendo esto en C ++, aunque estoy interesado en saber si la respuesta es diferente para C.
Gracias de antemano.
Actualización : esta respuesta no es correcta, ya que me falta el hecho de que volatile
garantías volatile
acceso a volatile
variables volatile
no se reordenan, pero no ofrecen tales garantías con respecto a otros accesos y manipulaciones no volatile
. Una valla de memoria proporciona tales garantías, y es necesaria para esta aplicación. Mi respuesta original está abajo, pero no actúes sobre ella. Vea esta respuesta para una buena explicación en el agujero en mi entendimiento que condujo a la siguiente respuesta incorrecta.
Respuesta original:
Sí, necesita volatile
en su declaración rt_data
; Cada vez que una variable puede modificarse fuera del flujo de control de un subproceso que accede a ella, debe declararse volatile
. Si bien es posible que pueda escapar sin volatile
ya que está copiando a un puntero local, al menos volatile
ayuda con la documentación y también inhibe algunas optimizaciones del compilador que pueden causar problemas. Considere el siguiente ejemplo, adoptado de DDJ :
volatile int a;
int b;
a = 1;
b = a;
Si es posible que a
a se le cambie el valor entre a=1
y b=a
, entonces a
debería declararse volatile
(a menos que, por supuesto, asignar un valor fuera de fecha a b
sea aceptable). El multihilo, particularmente con primitivos atómicos, constituye tal situación. La situación también se desencadena con variables modificadas por los manejadores de señales y por variables asignadas a ubicaciones de memoria impares (por ejemplo, registros de E / S de hardware). Véase también esta pregunta .
De lo contrario, me parece bien.
En C, probablemente usaría los primitivos atómicos proporcionados por GLib para esto. Utilizará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 ++.