2010-12-14 14 views
6

Estoy pensando en si es posible o no que la variable atómica cargue el valor anterior en el par adquirir-liberar. Supongamos que tenemos la variable atómica x, y almacenamos esa variable con semántica de lanzamiento y luego la cargamos con semántica de adquisición ¿es posible en teoría leer el valor anterior?adquisición-liberación par ejecución fuera de servicio

std::atomic<int> x = 0; 

void thread_1() 
{ 
    x.store(1, std::memory_order_release); 
} 
void thread_2() 
{ 
    assert(x.load(std::memory_order_acquire) != 0); 
} 

si hilo función 1 se termina cuando hilo cargas de 2 x (lo que el nuevo valor se almacena) ¿Es posible para el hilo 2 para cargar valor antiguo de x? En otras palabras, si el almacenamiento real en x se realiza antes de que la carga sea posible para afirmar disparar?

Por lo que he entendido por artículos en internet, es posible, pero no puedo entender por qué. La valla de memoria generada por la tienda en x garantías para vaciar la memoria intermedia de la tienda, mientras que la validación de memoria en la carga de x garantiza la invalidación de la línea de caché, por lo que debe leer el valor actualizado.

añade

¿Significa que adquieren de liberación por sí misma no tiene ningún pedido forzada? Es todo lo que se hizo antes de que se produzca el lanzamiento antes del lanzamiento y todo lo que se hace después de adquirir se producirá después de él, por lo que el par adquirir-liberar impone el orden en las otras operaciones (¿por qué?). ¿Lo entendí bien? ¿Quiere decir que en la aserción código de abajo se garantiza que no dispare

std::atomic<int> x = 0; 
std::atomic<int> y = 0; 

void thread_1() 
{ 
    y.store(1, std::memory_order_relaxed); 
    x.store(1, std::memory_order_release); 
} 
void thread_2() 
{ 
    x.load(std::memory_order_acquire); 
    assert(y.load(std::memory_order_relaxed) != 0); 
} 

por supuesto de nuevo si el hilo 1 ya se terminó la tienda. Si reemplazamos x.load con while (x.load() == 0), esto funcionará al 100%, pero no sé qué causa que esto funcione.

¿Y si puedo reemplazar el código con el código de abajo

std::atomic<int> x = 0; 

void thread_1() 
{ 
    x.exchange(1, std::memory_order_acq_rel); 
} 
void thread_2() 
{ 
    assert(x.exchange(0, std::memory_order_acq_rel) != 0); 
} 

¿Cambia algo?

Gracias.

+0

Quiero saber si no se garantiza que no se dispare (por supuesto en caso de que la tienda real se haya realizado antes de la carga). – axl

+0

sí, su edición ya lo había aclarado. Mi comentario fue escrito antes de que tu edición fuera mostrada. –

Respuesta

5

Usted podría considerar las funciones de almacenamiento/carga con la liberación/captar el fin de memoria como el siguiente pseudo-código:

template<class T> 
struct weak_atomic 
{ 
    void store(T newValue) 
    { 
     ReleaseBarrier(); 
     m_value = newValue; 
    } 

    T load() 
    { 
     T value = m_value; 
     AcquireBarrier(); 
     return value;  
    } 

    volatile T m_value; 
} 

Dijiste

valla de memoria generada por tienda para x garantías a vacío Almacenar almacenamiento intermedio

Según tengo entendido, la barrera de memoria de liberación hará que la CPU descargue su memoria intermedia de tienda , pero se hará antes de aplicando nuevo valor a x. Por lo tanto, parece posible leer el valor antiguo de x por otra CPU.

De todos modos, Átomos débiles es un área muy compleja. Asegúrese de comprender las barreras de memoria antes de continuar con la programación sin bloqueos.

AÑADIDO

Parece todavía está confundido con las barreras de memoria. Este es un ejemplo bastante común de su uso.

volatile int x; 
volatile bool ok; 

void thread_1() 
{ 
    x = 100; 
    ok = true; 
} 

void thread_2() 
{ 
    if (ok) 
    { 
     assert(x == 100); 
    } 
} 

Debido a la ejecución fuera de orden puede obtener la siguiente secuencia:

thread 1 sets ok to true 
thread 2 checks ok is true and reads some garbage from x 
thread 1 sets x to 100 but it is too late 

Otra posible secuencia:

thread 2 reads some garbage from x 
thread 2 checks for ok value 

Nos puede arreglar eso con la liberación y adquirir las barreras de memoria .

volatile int x; 
volatile bool ok; 

void thread_1() 
{ 
    x = 100; 
    ReleaseBarrier(); 
    ok = true; 
} 

void thread_2() 
{ 
    if (ok) 
    { 
     AcquireBarrier(); 
     assert(x == 100); 
    } 
} 

ReleaseBarrier() garantiza que las escrituras de memoria no puede saltar sobre la barrera. Significa que ok solo está configurado en true cuando x ya contiene un valor válido.

AcquireBarrier() garantiza que las lecturas de memoria no pueden saltar la barrera. Significa que el valor de x solo se lee después de comprobar el estado ok.

Así es como se pretende utilizar el par de liberación/adquisición. Podemos reescribir este ejemplo con mi weak_atomic.

volatile int x; 
weak_atomic<bool> ok; 

void thread_1() 
{ 
    x = 100; 
    ok.store(true); 
} 

void thread_2() 
{ 
    if (ok.load()) 
    { 
     assert(x == 100); 
    } 
} 
+0

Hm. Podemos reemplazar pseudocódigo con el real. Podemos poner std :: atomic_thread_fence (std :: memory_order_release) allí y hacer que la operación en sí sea "relajada". En realidad tienes razón, la valla se aplicó antes, no pensé en eso, pero no entiendo cómo tiene sentido esto. Se supone que la barrera de liberación está allí para publicar los cambios, pero en realidad no hace nada. Actualizo mi pregunta anterior. – axl

+0

@axl: He agregado un ejemplo de cómo se pretende utilizar el par de liberación/adquisición. Espero que ayude. – Stas

+0

@Stas: En realidad, quise decir algo diferente, probablemente fallé al explicar eso, porque una vez que leí mi pregunta hoy, también estaba confundido :) El ejemplo clásico de barreras de memoria es usar while loop, que está ausente en mi ejemplo. Mi pregunta era: ¿hay garantía de que la carga etiquetada con memory_order_acquire verá inmediatamente la tienda, etiquetada con memory_order_release (como con el modelo secuencialmente consistente). – axl

Cuestiones relacionadas