threads - thread library c++
¿Qué es exactamente std:: atomic? (2)
Entiendo que
std::atomic<>
es un objeto atómico.
¿Pero atómico hasta qué punto?
A mi entender, una operación puede ser atómica.
¿Qué se entiende exactamente por hacer un objeto atómico?
Por ejemplo, si hay dos subprocesos que ejecutan simultáneamente el siguiente código:
a = a + 12;
Entonces, ¿toda la operación (digamos
add_twelve_to(int)
) es atómica?
¿O se realizan cambios en la variable atómica (por lo tanto,
operator=()
)?
Entiendo que
std::atomic<>
hace que un objeto sea atómico.
Es una cuestión de perspectiva ... no puede aplicarlo a objetos arbitrarios y hacer que sus operaciones se vuelvan atómicas, pero se pueden usar las especializaciones proporcionadas para (la mayoría) tipos y punteros integrales.
a = a + 12;
std::atomic<>
no (usa expresiones de plantilla para) simplificar esto a una sola operación atómica, en su lugar, el
operator T() const volatile noexcept
miembro
operator T() const volatile noexcept
hace una
load()
atómica
load()
de
a
, luego se agrega doce, y
operator=(T t) noexcept
hace una
store(t)
.
Cada instanciación y especialización completa de std::atomic<> representa un tipo en el que diferentes subprocesos pueden operar simultáneamente (sus instancias), sin generar un comportamiento indefinido:
Los objetos de tipo atómico son los únicos objetos de C ++ que están libres de razas de datos; es decir, si un hilo escribe en un objeto atómico mientras otro hilo lee de él, el comportamiento está bien definido.
Además, los accesos a objetos atómicos pueden establecer una sincronización entre subprocesos y ordenar accesos a la memoria no atómica según lo especificado por
std::memory_order
.
std::atomic<>
envuelve operaciones que, en pre-C ++ 11 veces, tenían que realizarse usando (por ejemplo)
funciones entrelazadas
con MSVC o
bultinas atómicas
en caso de CCG.
Además,
std::atomic<>
le da más control al permitir varias
órdenes de memoria
que especifican restricciones de orden y sincronización.
Si desea leer más sobre el modelo atómico y de memoria de C ++ 11, estos enlaces pueden ser útiles:
- C ++ atómica y ordenamiento de memoria
- Comparación: programación sin bloqueo con atómicas en C ++ 11 frente a mutex y bloqueos RW
- C ++ 11 introdujo un modelo de memoria estandarizado. Qué significa eso? ¿Y cómo va a afectar la programación en C ++?
- Concurrencia en C ++ 11
Tenga en cuenta que, para casos de uso típicos, probablemente usaría operadores aritméticos sobrecargados u otro conjunto de ellos :
std::atomic<long> value(0);
value++; //This is an atomic op
value += 5; //And so is this
Debido a que la sintaxis del operador no le permite especificar el orden de la memoria, estas operaciones se realizarán con
std::memory_order_seq_cst
, ya que este es el orden predeterminado para todas las operaciones atómicas en C ++ 11. Garantiza la coherencia secuencial (orden global total) entre todas las atómicas. operaciones
Sin embargo, en algunos casos, esto puede no ser necesario (y nada es gratis), por lo que es posible que desee utilizar una forma más explícita:
std::atomic<long> value {0};
value.fetch_add(1, std::memory_order_relaxed); // Atomic, but there are no synchronization or ordering constraints
value.fetch_add(5, std::memory_order_release); // Atomic, performs ''release'' operation
Ahora, tu ejemplo:
a = a + 12;
no evaluará a una sola
a.load()
atómica: dará como resultado
a.load()
(que es atómico en sí mismo), luego la suma entre este valor y
12
y
a.store()
(también atómico) del resultado final.
Como señalé anteriormente,
std::memory_order_seq_cst
se usará aquí.
Sin embargo, si escribe
a += 12
, será una operación atómica (como señalé antes) y es aproximadamente equivalente a
a.fetch_add(12, std::memory_order_seq_cst)
.
En cuanto a tu comentario:
Un
int
regular tiene cargas atómicas y tiendas. ¿Cuál es el punto de envolverlo conatomic<>
?
Su declaración solo es cierta para las arquitecturas que brindan dicha garantía de atomicidad para tiendas y / o cargas.
Hay arquitecturas que no hacen esto.
Además, generalmente se requiere que las operaciones se realicen en una dirección alineada palabra / palabra para que atomic
std::atomic<>
sea algo que se garantice que sea atómico en
todas las
plataformas, sin requisitos adicionales.
Además, te permite escribir código como este:
void* sharedData = nullptr;
std::atomic<int> ready_flag = 0;
// Thread 1
void produce()
{
sharedData = generateData();
ready_flag.store(1, std::memory_order_release);
}
// Thread 2
void consume()
{
while (ready_flag.load(std::memory_order_acquire) == 0)
{
std::this_thread::yield();
}
assert(sharedData != nullptr); // will never trigger
processData(sharedData);
}
Tenga en cuenta que la condición de afirmación siempre será verdadera (y, por lo tanto, nunca se activará), por lo que siempre puede estar seguro de que los datos estarán listos después de
while
finalice el ciclo.
Eso es porque:
-
store()
para el indicador se realiza después de que se establecesharedData
(suponemos quegenerateData()
siempre devuelve algo útil, en particular, nunca devuelveNULL
) y usa el ordenstd::memory_order_release
:
memory_order_release
Una operación de almacenamiento con este orden de memoria realiza la operación de liberación : no se pueden reordenar lecturas o escrituras en el hilo actual después de este almacenamiento. Todas las escrituras en el hilo actual son visibles en otros hilos que adquieren la misma variable atómica
-
sharedData
se usa después dewhile
salga el bucle y, por lo tanto, después deload()
del indicador devolverá un valor distinto de cero.load()
usa el ordenstd::memory_order_acquire
:
std::memory_order_acquire
Una operación de carga con este orden de memoria realiza la operación de adquisición en la ubicación de memoria afectada: no se pueden reordenar lecturas o escrituras en el subproceso actual antes de esta carga. Todas las escrituras en otros hilos que liberan la misma variable atómica son visibles en el hilo actual .
Esto le brinda un control preciso sobre la sincronización y le permite especificar explícitamente cómo su código puede / no puede / no / se comportará. Esto no sería posible si la única garantía fuera la atomicidad misma. Especialmente cuando se trata de modelos de sincronización muy interesantes como el pedido de lanzamiento de consumo .