¿Qué es exactamente std :: atomic?

186
Noname 2015-08-13 16:00.

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 que un objeto sea 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 (entonces operator=())?

3 answers

206
Mateusz Grzejek 2015-08-13 16:55.

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 carreras 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 los objetos atómicos pueden establecer una sincronización entre subprocesos y ordenar los accesos a la memoria no atómica según lo especificado por std::memory_order.

std::atomic<>envuelve operaciones que, en tiempos anteriores a C ++ 11, tenían que realizarse utilizando (por ejemplo) funciones entrelazadas con MSVC o bultinas atómicas en el caso de GCC.

Además, std::atomic<>le brinda más control al permitir varias órdenes de memoria que especifican restricciones de sincronización y ordenación. Si desea leer más sobre el modelo atómico y de memoria de C ++ 11, estos enlaces pueden ser útiles:

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 operaciones atómicas.

En algunos casos, sin embargo, es posible que esto no sea necesario (y nada es gratuito), 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 operación atómica: dará como resultado a.load()(que es atómico en sí mismo), luego la suma entre este valor y 12y a.store()(también atómico) del resultado final. Como señalé anteriormente, std::memory_order_seq_cstse utilizará 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 regular inttiene cargas atómicas y almacenes. ¿De qué sirve envolverlo atomic<>?

Su declaración solo es válida para 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 con palabra / palabra para que sea atómica, std::atomic<>algo que se garantiza que será 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 aserción siempre será verdadera (y, por lo tanto, nunca se activará), por lo que siempre puede estar seguro de que los datos están listos después de la whilesalida del ciclo. Eso es porque:

  • store()to the flag se realiza después de que sharedDatase establece (asumimos que generateData()siempre devuelve algo útil, en particular, nunca regresa NULL) y usa std::memory_order_releaseorder:

memory_order_release

Una operación de almacenamiento con esta orden de memoria realiza la operación de liberación : no se pueden reordenar lecturas o escrituras en el hilo actual después de esta tienda. Todas las escrituras en el hilo actual son visibles en otros hilos que adquieren la misma variable atómica

  • sharedDatase usa después de que el whilebucle sale y, por lo tanto, el load()indicador de from devolverá un valor distinto de cero. load()usa std::memory_order_acquireorden:

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 hilo actual antes de esta carga. Todas las escrituras en otros subprocesos que liberan la misma variable atómica son visibles en el subproceso actual .

Esto le da un control preciso sobre la sincronización y le permite especificar explícitamente cómo su código puede / no puede / no se comportará / 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 liberación-consumo .

21
Tony Delroy 2015-08-13 16:42.

Entiendo que eso std::atomic<>hace que un objeto sea atómico.

Eso es una cuestión de perspectiva ... no se puede aplicar a objetos arbitrarios y hacer que sus operaciones se vuelvan atómicas, pero se pueden usar las especializaciones proporcionadas para (la mayoría) de 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 noexceptmiembro hace un atómico load()de a, luego se agrega doce y operator=(T t) noexcepthace un store(t).

std::atomic existe porque muchas ISA tienen soporte de hardware directo para él

Lo que dice el estándar C ++ std::atomicse ha analizado en otras respuestas.

Así que ahora veamos qué se std::atomiccompila para obtener un tipo diferente de conocimiento.

La principal conclusión de este experimento es que las CPU modernas tienen soporte directo para operaciones de enteros atómicos, por ejemplo, el prefijo LOCK en x86, y std::atomicbásicamente existe como una interfaz portátil para esas instrucciones: ¿Qué significa la instrucción "bloquear" en el ensamblaje x86?En aarch64, se usaría LDADD .

Este soporte permite alternativas más rápidas a métodos más generales, como std::mutex, que puede hacer que las secciones de múltiples instrucciones más complejas sean atómicas, a costa de ser más lento que std::atomicporque std::mutexhace futexllamadas al sistema en Linux, que es mucho más lento que las instrucciones de usuario emitidas por std::atomic, ver también: ¿Std :: mutex crea una cerca?

Consideremos el siguiente programa multiproceso que incrementa una variable global a través de múltiples subprocesos, con diferentes mecanismos de sincronización dependiendo de qué preprocesador se utilice.

main.cpp

#include <atomic>
#include <iostream>
#include <thread>
#include <vector>

size_t niters;

#if STD_ATOMIC
std::atomic_ulong global(0);
#else
uint64_t global = 0;
#endif

void threadMain() {
    for (size_t i = 0; i < niters; ++i) {
#if LOCK
        __asm__ __volatile__ (
            "lock incq %0;"
            : "+m" (global),
              "+g" (i) // to prevent loop unrolling
            :
            :
        );
#else
        __asm__ __volatile__ (
            ""
            : "+g" (i) // to prevent he loop from being optimized to a single add
            : "g" (global)
            :
        );
        global++;
#endif
    }
}

int main(int argc, char **argv) {
    size_t nthreads;
    if (argc > 1) {
        nthreads = std::stoull(argv[1], NULL, 0);
    } else {
        nthreads = 2;
    }
    if (argc > 2) {
        niters = std::stoull(argv[2], NULL, 0);
    } else {
        niters = 10;
    }
    std::vector<std::thread> threads(nthreads);
    for (size_t i = 0; i < nthreads; ++i)
        threads[i] = std::thread(threadMain);
    for (size_t i = 0; i < nthreads; ++i)
        threads[i].join();
    uint64_t expect = nthreads * niters;
    std::cout << "expect " << expect << std::endl;
    std::cout << "global " << global << std::endl;
}

GitHub aguas arriba .

Compilar, ejecutar y desensamblar:

comon="-ggdb3 -O3 -std=c++11 -Wall -Wextra -pedantic main.cpp -pthread"
g++ -o main_fail.out                    $common
g++ -o main_std_atomic.out -DSTD_ATOMIC $common
g++ -o main_lock.out       -DLOCK       $common

./main_fail.out       4 100000
./main_std_atomic.out 4 100000
./main_lock.out       4 100000

gdb -batch -ex "disassemble threadMain" main_fail.out
gdb -batch -ex "disassemble threadMain" main_std_atomic.out
gdb -batch -ex "disassemble threadMain" main_lock.out

Salida de condición de carrera "incorrecta" extremadamente probable para main_fail.out:

expect 400000
global 100000

y salida determinista "correcta" de los demás:

expect 400000
global 400000

Desmontaje de main_fail.out:

   0x0000000000002780 <+0>:     endbr64 
   0x0000000000002784 <+4>:     mov    0x29b5(%rip),%rcx        # 0x5140 <niters>
   0x000000000000278b <+11>:    test   %rcx,%rcx
   0x000000000000278e <+14>:    je     0x27b4 <threadMain()+52>
   0x0000000000002790 <+16>:    mov    0x29a1(%rip),%rdx        # 0x5138 <global>
   0x0000000000002797 <+23>:    xor    %eax,%eax
   0x0000000000002799 <+25>:    nopl   0x0(%rax)
   0x00000000000027a0 <+32>:    add    $0x1,%rax
   0x00000000000027a4 <+36>:    add    $0x1,%rdx
   0x00000000000027a8 <+40>:    cmp    %rcx,%rax
   0x00000000000027ab <+43>:    jb     0x27a0 <threadMain()+32>
   0x00000000000027ad <+45>:    mov    %rdx,0x2984(%rip)        # 0x5138 <global>
   0x00000000000027b4 <+52>:    retq

Desmontaje de main_std_atomic.out:

   0x0000000000002780 <+0>:     endbr64 
   0x0000000000002784 <+4>:     cmpq   $0x0,0x29b4(%rip)        # 0x5140 <niters>
   0x000000000000278c <+12>:    je     0x27a6 <threadMain()+38>
   0x000000000000278e <+14>:    xor    %eax,%eax
   0x0000000000002790 <+16>:    lock addq $0x1,0x299f(%rip)        # 0x5138 <global>
   0x0000000000002799 <+25>:    add    $0x1,%rax
   0x000000000000279d <+29>:    cmp    %rax,0x299c(%rip)        # 0x5140 <niters>
   0x00000000000027a4 <+36>:    ja     0x2790 <threadMain()+16>
   0x00000000000027a6 <+38>:    retq   

Desmontaje de main_lock.out:

Dump of assembler code for function threadMain():
   0x0000000000002780 <+0>:     endbr64 
   0x0000000000002784 <+4>:     cmpq   $0x0,0x29b4(%rip)        # 0x5140 <niters>
   0x000000000000278c <+12>:    je     0x27a5 <threadMain()+37>
   0x000000000000278e <+14>:    xor    %eax,%eax
   0x0000000000002790 <+16>:    lock incq 0x29a0(%rip)        # 0x5138 <global>
   0x0000000000002798 <+24>:    add    $0x1,%rax
   0x000000000000279c <+28>:    cmp    %rax,0x299d(%rip)        # 0x5140 <niters>
   0x00000000000027a3 <+35>:    ja     0x2790 <threadMain()+16>
   0x00000000000027a5 <+37>:    retq

Conclusiones:

  • la versión no atómica guarda el global en un registro e incrementa el registro.

    Por lo tanto, al final, es muy probable que cuatro escrituras vuelvan a ser globales con el mismo valor "incorrecto" de 100000.

  • std::atomiccompila a lock addq. El prefijo LOCK hace lo siguiente: incbuscar, modificar y actualizar la memoria de forma atómica.

  • nuestro prefijo LOCK de ensamblado en línea explícito se compila en casi lo mismo que std::atomic, excepto que incse usa nuestro en lugar de add. No estoy seguro de por qué eligió GCC add, considerando que nuestro INC generó una decodificación 1 byte más pequeña.

ARMv8 podría usar LDAXR + STLXR o LDADD en CPU más nuevas: ¿Cómo inicio subprocesos en C simple?

Probado en Ubuntu 19.10 AMD64, GCC 9.2.1, Lenovo ThinkPad P51.

MORE COOL STUFF

La estrella de HGTV, Christina Hall, revela que tiene 'envenenamiento por mercurio y plomo' probablemente por voltear 'casas asquerosas'

La estrella de HGTV, Christina Hall, revela que tiene 'envenenamiento por mercurio y plomo' probablemente por voltear 'casas asquerosas'

La estrella de HGTV, Christina Hall, revela que le diagnosticaron envenenamiento por mercurio y plomo, probablemente debido a su trabajo como manipuladora de casas.

La estrella de 'Love Is Blind' Brennon Lemieux responde a los cargos de violencia doméstica

La estrella de 'Love Is Blind' Brennon Lemieux responde a los cargos de violencia doméstica

Recientemente salió a la luz un informe policial que acusa a la estrella de 'Love Is Blind', Brennon, de violencia doméstica. Ahora, Brennon ha respondido a los reclamos.

Wynonna Judd se dio cuenta de que ahora es la matriarca de la familia Judd en un momento festivo de pánico

Wynonna Judd se dio cuenta de que ahora es la matriarca de la familia Judd en un momento festivo de pánico

Conozca cómo Wynonna Judd se dio cuenta de que ahora es la matriarca de la familia mientras organizaba la primera celebración de Acción de Gracias desde que murió su madre, Naomi Judd.

Experto en lenguaje corporal explica los 'paralelos' entre Kate Middleton y la princesa Diana

Experto en lenguaje corporal explica los 'paralelos' entre Kate Middleton y la princesa Diana

Descubra por qué un destacado experto en lenguaje corporal cree que es fácil trazar "tales paralelismos" entre la princesa Kate Middleton y la princesa Diana.

Los láseres arrojan luz sobre por qué necesita cerrar la tapa antes de descargar

Los láseres arrojan luz sobre por qué necesita cerrar la tapa antes de descargar

Los inodoros arrojan columnas de aerosol invisibles con cada descarga. ¿Como sabemos? La prueba fue capturada por láseres de alta potencia.

The Secrets of Airline Travel Quiz

The Secrets of Airline Travel Quiz

Air travel is far more than getting from point A to point B safely. How much do you know about the million little details that go into flying on airplanes?

Where in the World Are You? Take our GeoGuesser Quiz

Where in the World Are You? Take our GeoGuesser Quiz

The world is a huge place, yet some GeoGuessr players know locations in mere seconds. Are you one of GeoGuessr's gifted elite? Take our quiz to find out!

¿Caduca el repelente de insectos?

¿Caduca el repelente de insectos?

¿Sigue siendo efectivo ese lote de repelente de insectos que te quedó del verano pasado? Si es así, ¿por cuánto tiempo?

Ponle una tapa. En realidad, ponle una tapa a todo. Consigue 12 tapas de cocina elásticas de silicona por $14. [Exclusivo]

Ponle una tapa. En realidad, ponle una tapa a todo. Consigue 12 tapas de cocina elásticas de silicona por $14. [Exclusivo]

Tapas elásticas de silicona de Tomorrow's Kitchen, paquete de 12 | $14 | Amazonas | Código promocional 20OFFKINJALids son básicamente los calcetines de la cocina; siempre perdiéndose, dejando contenedores huérfanos que nunca podrán volver a cerrarse. Pero, ¿y si sus tapas pudieran estirarse y adaptarse a todos los recipientes, ollas, sartenes e incluso frutas en rodajas grandes que sobran? Nunca más tendrás que preocuparte por perder esa tapa tan específica.

Cuéntanos tus mejores trucos de Washington, DC

Cuéntanos tus mejores trucos de Washington, DC

Hemos pirateado algunas ciudades industriales en esta columna, como Los Ángeles y Las Vegas. Ahora es el momento de una ciudad militar-industrial-compleja.

Un minorista está eliminando su sección de tallas grandes y mezclando tallas más grandes con todo lo demás

Un minorista está eliminando su sección de tallas grandes y mezclando tallas más grandes con todo lo demás

Un minorista está enlatando su sección de tallas grandes. Pero no están tomando la categoría solo en línea o descontinuándola por completo.

La mejor forma de guardar animales de peluche es dentro de un puf

La mejor forma de guardar animales de peluche es dentro de un puf

Entiendo totalmente, completamente si tienes una relación difícil con los animales de peluche. Son lindos, tienen valor sentimental y es difícil separarse de ellos.

Patinaje artístico de EE. UU. 'frustrado' por falta de decisión final en evento por equipos, pide una decisión justa

Patinaje artístico de EE. UU. 'frustrado' por falta de decisión final en evento por equipos, pide una decisión justa

El equipo está a la espera de las medallas que ganó en los Juegos Olímpicos de Invierno de 2022 en Beijing, ya que se está resolviendo un caso de dopaje que involucra a la patinadora artística rusa Kamila Valieva.

Los compradores de Amazon dicen que duermen 'como un bebé mimado' gracias a estas fundas de almohada de seda que cuestan tan solo $ 10

Los compradores de Amazon dicen que duermen 'como un bebé mimado' gracias a estas fundas de almohada de seda que cuestan tan solo $ 10

Miles de compradores de Amazon recomiendan la funda de almohada de seda Mulberry, y está a la venta en este momento. La funda de almohada de seda viene en varios colores y ayuda a mantener el cabello suave y la piel clara. Compre las fundas de almohada de seda mientras tienen hasta un 46 por ciento de descuento en Amazon

Se busca al corredor de los Bengals Joe Mixon por orden de arresto emitida por presuntamente apuntar con un arma de fuego a una mujer

Se busca al corredor de los Bengals Joe Mixon por orden de arresto emitida por presuntamente apuntar con un arma de fuego a una mujer

El jueves se presentó una denuncia de delito menor amenazante agravado contra Joe Mixon.

Profesor de la Universidad de Purdue arrestado por presuntamente traficar metanfetamina y proponer favores sexuales a mujeres

Profesor de la Universidad de Purdue arrestado por presuntamente traficar metanfetamina y proponer favores sexuales a mujeres

El Departamento de Policía de Lafayette comenzó a investigar a un profesor de la Universidad de Purdue en diciembre después de recibir varias denuncias de un "hombre sospechoso que se acercaba a una mujer".

Concept Drift: el mundo está cambiando demasiado rápido para la IA

Concept Drift: el mundo está cambiando demasiado rápido para la IA

Al igual que el mundo que nos rodea, el lenguaje siempre está cambiando. Mientras que en eras anteriores los cambios en el idioma ocurrían durante años o incluso décadas, ahora pueden ocurrir en cuestión de días o incluso horas.

India me está pateando el culo

India me está pateando el culo

Estoy de vuelta por primera vez en seis años. No puedo decirte cuánto tiempo he estado esperando esto.

ℝ

“And a river went out of Eden to water the garden, and from thence it was parted and became into four heads” Genesis 2:10. ? The heart is located in the middle of the thoracic cavity, pointing eastward.

¿Merrick Garland le ha fallado a Estados Unidos?

Es más de la mitad de la presidencia de Biden. ¿Qué está esperando Merrick Garland?

¿Merrick Garland le ha fallado a Estados Unidos?

Creo, un poco tarde en la vida, en dar oportunidades a la gente. Generosamente.

Language