2009-04-10 30 views
6

Me encuentro con una situación en la que necesito la suma atómica de dos valores en la memoria. El código que he heredado es la siguiente:¿Hay alguna forma en que pueda hacer dos lecturas atómicas?

int a = *MemoryLocationOne; 
memory_fence(); 
int b = *MemoryLocationTwo; 
return (a + b) == 0; 

El individuo lee de a y b son atómica, y todas las escrituras en otras partes del código para estas dos posiciones de memoria son también lockless atómica. Sin embargo, el problema es que los valores de las dos ubicaciones pueden cambiar y cambian entre las dos lecturas.

Entonces, ¿cómo hago esta operación atómica? Lo sé todo sobre CAS, pero suele implicar que las operaciones de lectura-modificación-escritura sean atómicas y eso no es exactamente lo que quiero hacer aquí.

¿Hay alguna manera de hacerlo, o es la mejor opción para refactorizar el código para que solo tenga que marcar un valor?

Editar: Gracias, no mencioné que quería hacer esto sin bloqueos en la primera revisión, pero algunas personas lo detectaron después de mi segunda revisión. Sé que nadie cree en las personas cuando dicen cosas como esta, pero no puedo usar cerraduras prácticamente. Tendría que emular un mutex con átomos y eso sería más trabajo que refacturar el código para hacer un seguimiento de un valor en lugar de dos.

Por ahora mi método de investigación implica aprovechar el hecho de que los valores son consecutivos y tomarlos atómicamente con una lectura de 64 bits, que estoy seguro son atómicos en mis plataformas de destino. Si alguien tiene nuevas ideas, ¡por favor contribuya! Gracias.

Respuesta

3

Si realmente necesita asegurarse de que a y b no cambian mientras se está haciendo esta prueba, entonces es necesario utilizar la misma sincronización para todas acceso a a y b. Esa es tu única opción. Cada lectura y cada escritura en cualquiera de estos valores debe usar la misma valla de memoria, sincronizador, semáforo, bloqueo de división de tiempo o cualquier mecanismo que se utilice.

Con esto, se puede asegurar que si:

memory_fence_start(); 
int a = *MemoryLocationOne; 
int b = *MemoryLocationTwo; 
int test = (a + b) == 0; 
memory_fence_stop(); 

return test; 

continuación a no va a cambiar mientras se está leyendo b. Pero, una vez más, debe utilizar el mismo mecanismo de sincronización para , todos, acceso a a y b.

Para reflejar una edición posterior a su pregunta que está buscando un método sin bloqueo, bueno, depende completamente del procesador que está usando y de cuánto tiempo están a y b y si estas ubicaciones de memoria son consecutivos y están alineados correctamente.

Suponiendo que estos son consecutivos en la memoria y 32 bits cada uno y que su procesador tiene una lectura atómica de 64 bits, puede emitir una lectura atómica de 64 bits para leer los dos valores, analizar los dos valores fuera de la Valor de 64 bits, haga los cálculos y devuelva lo que desea devolver.Suponiendo que nunca necesite una actualización atómica para "a y b al mismo tiempo", sino solo actualizaciones atómicas para "a" o para "b" de forma aislada, esto hará lo que desee sin bloqueos.

+0

Ocurren que ser consecutivos 32 direcciones de bits y los procesadores que necesito para codificar para trabajar en tener atómica de 64 bits lee si alineado correctamente, por lo esto parece el camino a seguir. –

+0

Ah, en ese caso puede escribir algún ensamblaje o simplemente verificar la salida de ensamblaje de su compilador para verificar que el uso de un tipo de 64 bits haga lo correcto. En ese caso, puede ir sin candado. – Eddie

3

Debería asegurarse de que en todos los lugares donde se leen o escriben los dos valores, están rodeados por una barrera de memoria (bloqueo o sección crítica).

// all reads... 
lock(lockProtectingAllAccessToMemoryOneAndTwo) 
{ 
    a = *MemoryLocationOne; 
    b = *MemoryLocationTwo; 
} 

...

// all writes... 
lock(lockProtectingAllAccessToMemoryOneAndTwo) 
{ 
    *MemoryLocationOne = someValue; 
    *MemoryLocationTwo = someOtherValue; 
} 
1

Realmente no hay forma de hacerlo sin un candado. Ningún procesador tiene una doble lectura atómica, hasta donde yo sé.

+0

Incluso si un procesador tuviera una doble lectura atómica, eso solo ayudaría si las dos direcciones de memoria en cuestión estuvieran alineadas y fueran consecutivas. – Eddie

+0

No, un procesador podría permitir una lectura atómica de dos ubicaciones de memoria arbitrarias. – Zifre

+0

No conozco ningún procesador que permita una lectura atómica garantizada de ubicaciones de memoria arbitrarias no consecutivas, incluso en un entorno multiprocesador, pero tomaré su palabra para ello. – Eddie

3

Si tiene como objetivo x86, puede usar la compatibilidad de comparar/intercambiar de 64 bits y empaquetar ambas int en una sola palabra de 64 bits.

En Windows, se podría hacer esto:

// Skipping ensuring padding. 
union Data 
{ 
    struct members 
    { 
     int a; 
     int b; 
    }; 

    LONGLONG _64bitData; 
}; 

Data* data; 


Data captured; 

do 
{ 
    captured = *data; 
    int result = captured.members.a + captured.members.b; 
} while (InterlockedCompareExchange64((LONGLONG*)&data->_64bitData, 
        captured._64BitData, 
        captured._64bitData) != captured._64BitData); 

realmente feo. Sugiero usar un candado, mucho más fácil de mantener.

EDIT: Para actualizar y leer las partes individuales:

data->members.a = 0; 
fence(); 

data->members.b = 0; 
fence(); 

int captured = data->members.a; 

int captured = data->members.b; 
+0

Esto es cierto, pero estás jodido cuando necesitas actualizar solo uno de esos elementos. –

+1

No, realiza lecturas/escrituras atómicas subyacentes en las partes de 32 bits. – Michael

+1

Y en caso de que no estuviera claro, mi respuesta fue mostrar cómo se podía hacer: siempre recomiendo evitar los algoritmos sin bloqueo. – Michael

Cuestiones relacionadas