¿Puedo realizar operaciones aritméticas en una variable atómica directamente?
Dado que encuentro que la biblioteca estándar de C proporciona muchas funciones de utilidad como atomic_fetch_add para realizar la suma entre una variable atómica y una variable no atómica. Pero tengo curiosidad, ya que la variable es atómica, ¿puedo tener operaciones aritméticas directamente sobre ella? Como en el código que se muestra a continuación:
#include <threads.h> #include <stdio.h> #include <stdatomic.h> atomic_int i = 0; int run(void* v) { i += 100; // <- is this operaiton thread-safe? // atomic_fetch_add(&i, 100); printf("%d\n", i); return thrd_success; } int main(void) { thrd_t thread; thrd_create(&thread, run, NULL); thrd_join(thread, NULL); return 0; }Las asignaciones compuestas a variables con tipos atómicos están explícitamente permitidas en C2011: citando N1570 §6.15.6.2p3
Una asignación compuesta de la forma
E1 op= E2es equivalente a la expresión de asignación simpleE1 = E1 op (E2), excepto que el lvalueE1se evalúa solo una vez, y con respecto a una llamada de función de secuencia indeterminada, la operación de un la asignación compuesta es una evaluación única. SiE1tiene un tipo atómico, la asignación compuesta es una operación de lectura, modificación y escritura con semántica de orden de memoriamemory_order_seq_cst. [nota al pie 113]
Énfasis mío. La nota al pie 113 continúa dando un ejemplo específico de cómo E1 op= E2 puede traducirse en operaciones primitivas <stdatomic.h> cuando E1 es un tipo atómico.
Los otros operadores que estoy seguro que se pueden aplicar a los tipos atómicos son postfix ++ y -- , que también están garantizados para realizar una lectura-modificación-escritura atómica (§6.5.2.4), y una asignación simple, que está garantizada para realizar una carga atómica o almacenar según corresponda (§6.2.6.1).
Precaución: No se garantiza que los prefijos ++ y -- realicen una lectura-modificación-escritura atómica (compare 6.5.2.4 con 6.5.3.1).
Leí esta oración de 6.2.6.1
Las cargas y almacenes de objetos con tipos atómicos se realizan con la semántica
memory_order_seq_cst.
como lo que implica que es válido usar un lvalue atómico como operando para la mayoría de los otros operadores, y se realiza una carga atómica, después de lo cual el valor no se trata de manera especial. No me cites en esta parte.
C++ puede tener reglas diferentes. Dejaré la exégesis del estándar C++ a otra persona.
El i += 10; mantiene la atomicidad de i , porque se usa como una expresión de valor l. De cppreference (negrita mía):
Los operadores integrados de incremento y decremento y la asignación compuesta son operaciones atómicas de lectura, modificación y escritura con un orden total secuencialmente coherente (como si se usara memory_order_seq_cst). Si se desea una semántica de sincronización menos estricta, se pueden usar las funciones de biblioteca estándar en su lugar.
El ejemplo dado en la página vinculada usa la operación de incremento (pre) incorporada en la variable acnt , pero también podría haber usado una asignación compuesta, como lo hace su código.
Sin embargo, las operaciones aritméticas más complejas pueden hacer que la variable i pierda su atomicidad, si no se usa estrictamente como una expresión de valor l. De la misma página:
Las propiedades atómicas solo son significativas para las expresiones lvalue. La conversión de valor L a valor R (que modela una memoria leída desde una ubicación atómica a un registro de CPU) elimina la atomicidad junto con otros calificadores.