Guía de concurrencia de C ++ std :: atomic

Introducción básica a std :: atomic

std :: atomic es una clase de plantilla. Un objeto atómico con plantilla de tipo T encapsula un valor de tipo T.

plantilla estructura atómica;

La característica principal de los objetos de tipo atómico es que el acceso desde diferentes subprocesos no provoca carreras de datos. Por lo tanto, acceder a un objeto atómico desde diferentes subprocesos es un comportamiento bien definido y, en general, para los tipos no atómicos, el acceso simultáneo a un objeto (si no se realiza una operación de sincronización) dará como resultado un comportamiento indefinido.

std :: constructor atómico

El constructor de std :: atomic es el siguiente:
Inserte la descripción de la imagen aquí

  1. El constructor predeterminado, el objeto std :: atomic creado por el constructor predeterminado está en el estado no inicializado, y el objeto std :: atomic en el estado no inicializado se puede inicializar mediante la función atomic_init.
  2. Inicialice el constructor, inicialice un objeto std :: atomic por tipo T.
  3. El constructor de copias está deshabilitado.

Considere el siguiente ejemplo:

#include <iostream>       // std::cout
#include <atomic>         // std::atomic, std::atomic_flag, ATOMIC_FLAG_INIT
#include <thread>         // std::thread, std::this_thread::yield
#include <vector>         // std::vector
 
// 由 false 初始化一个 std::atomic<bool> 类型的原子变量
std::atomic<bool> ready(false);
std::atomic_flag winner = ATOMIC_FLAG_INIT;
 
void do_count1m(int id)
{
    
    
    while (!ready) {
    
     
        std::this_thread::yield();  // 等待 ready 变为 true.
    }
    
    for (volatile int i=0; i<1000000; ++i) {
    
    } // 计数
 
    if (!winner.test_and_set()) {
    
    
      std::cout << "thread #" << id << " won!\n";
    }
}
 
int main ()
{
    
    
    std::vector<std::thread> threads;
    std::cout << "spawning 10 threads that count to 1 million...\n";
    
    for (int i=1; i<=10; ++i) 
        threads.push_back(std::thread(do_count1m,i));
        
    ready = true;
 
    for (auto& th : threads) 
        th.join();
        
    return 0;
}

función std :: atomic :: operator = ()

La función de operación de asignación de std :: atomic se define de la siguiente manera: Se
Inserte la descripción de la imagen aquí
puede ver que la operación de copia de asignación ordinaria se ha deshabilitado. Pero se puede asignar una variable de tipo T a la variable de tipo atómico correspondiente (equivalente a la conversión implícita), la operación es atómica y el orden de memoria (Orden de memoria) tiene como valor predeterminado la coherencia secuencial (std :: memory_order_seq_cst), si es necesario especificar Para otras secuencias de memoria, use std :: atomic :: store ().

#include <iostream>             // std::cout
#include <atomic>               // std::atomic
#include <thread>               // std::thread, std::this_thread::yield
 
std::atomic<int> foo(0);
 
void set_foo(int x)
{
    
    
    foo = x; // 调用 std::atomic::operator=().
}
 
void print_foo()
{
    
    
    while (foo == 0) {
    
     // wait while foo == 0
        std::this_thread::yield();
    }
    std::cout << "foo: " << foo << '\n';
}
 
int main()
{
    
    
    std::thread first(print_foo);
    std::thread second(set_foo, 10);
    
    first.join();
    second.join();
    
    return 0;
}

STD básico :: operaciones de tipo atómico

is_lock_free

bool is_lock_free() const volatile noexcept;
bool is_lock_free() const noexcept;

Determine si el objeto std :: atomic tiene características sin bloqueo. Si un objeto satisface la función sin bloqueo, no causará bloqueo de subprocesos cuando varios subprocesos accedan al objeto. (Puede utilizar algún tipo de método de memoria transaccional para lograr funciones sin bloqueo).

Tienda

void store (T val, memory_order sync = memory_order_seq_cst) volatile noexcept;
void store (T val, memory_order sync = memory_order_seq_cst) noexcept;

Para modificar el valor encapsulado, la función std :: atomic :: store copia el parámetro val de tipo T al valor encapsulado por el objeto atómico. T es el parámetro de plantilla de clase std :: atomic. Además, el parámetro sync especifica el orden de la memoria y los valores posibles son los siguientes:
Inserte la descripción de la imagen aquí
Consulte el siguiente ejemplo:

#include <iostream>       // std::cout
#include <atomic>         // std::atomic, std::memory_order_relaxed
#include <thread>         // std::thread
 
std::atomic<int> foo(0);  // 全局的原子对象 foo
 
void set_foo(int x)
{
    
    
    foo.store(x, std::memory_order_relaxed);     // 设置(store) 原子对象 foo 的值
}
 
void print_foo()
{
    
    
    int x;
    do {
    
    
        x = foo.load(std::memory_order_relaxed); // 读取(load) 原子对象 foo 的值
    } while (x == 0);
    
    std::cout << "foo: " << x << '\n';
}
 
int main ()
{
    
    
    std::thread first(print_foo);     // 线程 first 打印 foo 的值
    std::thread second(set_foo, 10);  // 线程 second 设置 foo 的值
    
    first.join();
    second.join();
    
    return 0;
}

carga

T load (memory_order sync = memory_order_seq_cst) const volatile noexcept;
T load (memory_order sync = memory_order_seq_cst) const noexcept;

Lea el valor encapsulado, la sincronización de parámetros establece el orden de la memoria (Orden de la memoria), los valores posibles son los siguientes:
Inserte la descripción de la imagen aquí
consulte el ejemplo de tienda anterior.

operador T

operator T() const volatile noexcept;
operator T() const noexcept;

Similar a la función de carga, también lee el valor encapsulado. El operador T () es una operación de conversión de tipos. El orden de memoria predeterminado es std :: memory_order_seq_cst. Si necesita especificar otros órdenes de memoria, debe usar la función load () .
Considere el siguiente ejemplo:

#include <iostream>       // std::cout
#include <atomic>         // std::atomic
#include <thread>         // std::thread, std::this_thread::yield
 
std::atomic<int> foo(0);
std::atomic<int> bar(0);
 
void set_foo(int x)
{
    
    
    foo = x;
}
 
void copy_foo_to_bar()
{
    
    
 
    // 如果 foo == 0,则该线程 yield,
    // 在 foo == 0 时, 实际也是隐含了类型转换操作,
    // 因此也包含了 operator T() const 的调用.
    while (foo == 0) 
        std::this_thread::yield();
 
    // 实际调用了 operator T() const, 将foo 强制转换成 int 类型,
    // 然后调用 operator=().
    bar = static_cast<int>(foo);
}
 
void print_bar()
{
    
    
    // 如果 bar == 0,则该线程 yield,
    // 在 bar == 0 时, 实际也是隐含了类型转换操作,
    // 因此也包含了 operator T() const 的调用.
    while (bar == 0) 
        std::this_thread::yield();
    std::cout << "bar: " << bar << '\n';
}
 
int main ()
{
    
    
    std::thread first(print_bar);
    std::thread second(set_foo, 10);
    std::thread third(copy_foo_to_bar);
 
    first.join();
    second.join();
    third.join();
    return 0;
}

intercambio

T exchange (T val, memory_order sync = memory_order_seq_cst) volatile noexcept;
T exchange (T val, memory_order sync = memory_order_seq_cst) noexcept;

Leer y modificar el valor encapsulado, el intercambio reemplazará el valor especificado por val con el valor encapsulado por el objeto atómico antes, y devolverá el valor encapsulado por el objeto atómico antes. Todo el proceso es atómico (por lo que la operación de intercambio también se llama lectura - operación de modificación-escritura).
El parámetro de sincronización especifica el orden de la memoria (Orden de la memoria) y los valores posibles son los siguientes:
Inserte la descripción de la imagen aquí
Consulte el ejemplo a continuación, cada subproceso cuenta hasta 1M, y el subproceso que completa la tarea de conteo primero imprime su propia ID.

#include <iostream>       // std::cout
#include <atomic>         // std::atomic
#include <thread>         // std::thread
#include <vector>         // std::vector
 
std::atomic<bool> ready(false);
std::atomic<bool> winner(false);
 
void count1m (int id)
{
    
    
    while (!ready) {
    
    }                      // wait for the ready signal
    for (int i = 0; i < 1000000; ++i) {
    
    }   // go!, count to 1 million
    
    if (!winner.exchange(true)) {
    
     
        std::cout << "thread #" << id << " won!\n"; 
    }
};
 
int main ()
{
    
    
    std::vector<std::thread> threads;
    std::cout << "spawning 10 threads that count to 1 million...\n";
    
    for (int i = 1; i <= 10; ++i) 
        threads.push_back(std::thread(count1m,i));
    
    ready = true;
    
    for (auto& th : threads) 
        th.join();
 
    return 0;
}

compare_exchange_weak

Inserte la descripción de la imagen aquí
Compare e intercambie si el valor encapsulado (débil) es igual al valor especificado por el parámetro esperado, si:

  • Si son iguales, reemplace el valor anterior del objeto atómico con val.
  • Si no es igual, reemplace el valor esperado con el valor anterior del objeto atómico. Por lo tanto, después de llamar a esta función, si el valor encapsulado por el objeto atómico no es igual al valor especificado por el parámetro esperado, el contenido esperado es el antiguo valor del objeto atómico.

Esta función generalmente lee el valor encapsulado por el objeto atómico. Si la comparación es verdadera (es decir, el valor del objeto atómico es igual al esperado), se reemplaza el valor anterior del objeto atómico, pero toda la operación es atómica. Leer y modificar el valor en un hilo Cuando se usa un objeto atómico, otro hilo no puede leer o modificar el objeto atómico.

En el caso (2), la elección del orden de memoria depende del resultado de la operación de comparación. Si el resultado de la comparación es verdadero (es decir, el valor del objeto atómico es igual al esperado), el orden de memoria especificado por el parámetro el éxito está seleccionado, de lo contrario, el parámetro se selecciona error La secuencia de memoria especificada.

Tenga en cuenta que esta función compara directamente el valor encapsulado por el objeto atómico con el contenido físico del parámetro esperado. Por lo tanto, en algunos casos, la operación de comparación del objeto es igual cuando se juzga por el operador == (), pero puede fallar cuando juzgado por compare_exchange_weak debido al objeto El contenido físico subyacente puede tener alineación de bits u otras representaciones lógicas que son iguales pero representan físicamente valores diferentes (por ejemplo, verdadero y 2 o 3, ambos representan "verdadero" lógicamente, pero el las representaciones de los dos no son iguales en términos físicos).

A diferencia de compare_exchange_strong, la versión débil de la operación de comparar e intercambiar permite que el valor encapsulado por el objeto atómico sea el mismo que el contenido físico del parámetro esperado, pero aún así devuelve falso, pero esto es aceptable bajo ciertos algoritmos que requieren bucle y el rendimiento de compare_exchange_weak es mejor en algunas plataformas. Si el juicio de compare_exchange_weak ocurre fallas espurias, incluso si el valor encapsulado por el objeto atómico es el mismo que el contenido físico del parámetro esperado, el resultado de la operación de juicio es falso, la función compare_exchange_weak devuelve falso y el valor de el parámetro esperado no cambiará.

Para algunos algoritmos que no necesitan usar operaciones de bucle, generalmente es mejor usar compare_exchange_strong. Además, el orden de memoria de esta función se especifica mediante el parámetro de sincronización. Las condiciones opcionales son las siguientes:
Inserte la descripción de la imagen aquí
Consulte el siguiente ejemplo:

#include <iostream>       // std::cout
#include <atomic>         // std::atomic
#include <thread>         // std::thread
#include <vector>         // std::vector
 
// a simple global linked list:
struct Node {
    
     
    int value; 
    Node* next; 
};

std::atomic<Node*> list_head(nullptr);
 
void append(int val)
{
    
    
    // append an element to the list
    Node* newNode = new Node{
    
    val, list_head};
 
    // next is the same as: list_head = newNode, but in a thread-safe way:
    while (!list_head.compare_exchange_weak(newNode->next,newNode)) {
    
    }
    // (with newNode->next updated accordingly if some other thread just appended another node)
}
 
int main ()
{
    
    
    // spawn 10 threads to fill the linked list:
    std::vector<std::thread> threads;
    for (int i = 0; i < 10; ++i) 
        threads.push_back(std::thread(append, i));
        
    for (auto& th : threads) 
        th.join();
 
    // print contents:
    for (Node* it = list_head; it!=nullptr; it=it->next)
        std::cout << ' ' << it->value;
 
    std::cout << '\n';
 
    // cleanup:
    Node* it; 
    while (it=list_head) {
    
    
        list_head=it->next; 
        delete it;
    }
 
    return 0;
}

comparar_intercambio_fuerte

Inserte la descripción de la imagen aquí
Compare e intercambie si el valor encapsulado (fuerte) es igual al valor especificado por el parámetro esperado, si:

  • Si son iguales, reemplace el valor anterior del objeto atómico con val.
  • Si no es igual, reemplace el valor esperado con el valor anterior del objeto atómico. Por lo tanto, después de llamar a esta función, si el valor encapsulado por el objeto atómico no es igual al valor especificado por el parámetro esperado, el contenido esperado es el antiguo valor del objeto atómico.

Esta función generalmente lee el valor encapsulado por el objeto atómico. Si la comparación es verdadera (es decir, el valor del objeto atómico es igual al esperado), se reemplaza el valor anterior del objeto atómico, pero toda la operación es atómica. Leer y modificar el valor en un hilo Cuando se usa un objeto atómico, otro hilo no puede leer o modificar el objeto atómico.

En el caso (2), la elección del orden de memoria depende del resultado de la operación de comparación. Si el resultado de la comparación es verdadero (es decir, el valor del objeto atómico es igual al esperado), el orden de memoria especificado por el parámetro el éxito está seleccionado, de lo contrario, el parámetro se selecciona error La secuencia de memoria especificada.

Tenga en cuenta que esta función compara directamente el valor encapsulado por el objeto atómico con el contenido físico del parámetro esperado. Por lo tanto, en algunos casos, la operación de comparación del objeto es igual cuando se juzga por el operador == (), pero puede fallar cuando juzgado por compare_exchange_weak debido al objeto El contenido físico subyacente puede tener alineación de bits u otras representaciones lógicas que son iguales pero representan físicamente valores diferentes (por ejemplo, verdadero y 2 o 3, ambos representan "verdadero" lógicamente, pero el las representaciones de los dos no son iguales en términos físicos).

A diferencia de compare_exchange_weak, la operación de comparar e intercambiar de la versión fuerte no está permitida (falsamente) para devolver falso, es decir, el valor encapsulado por el objeto atómico es el mismo que el contenido físico del parámetro esperado, y la operación de comparación debe ser verdad. Sin embargo, en algunas plataformas, si el algoritmo en sí necesita una operación de bucle para verificar, el rendimiento de compare_exchange_weak será mejor.

Por lo tanto, para algunos algoritmos que no necesitan usar operaciones de bucle, generalmente es mejor usar compare_exchange_strong. Además, el orden de memoria de esta función se especifica mediante el parámetro de sincronización. Las condiciones opcionales son las siguientes:
Inserte la descripción de la imagen aquí
Consulte el siguiente ejemplo:

#include <iostream>       // std::cout
#include <atomic>         // std::atomic
#include <thread>         // std::thread
#include <vector>         // std::vector
 
// a simple global linked list:
struct Node {
    
     
    int value; 
    Node* next; 
};
std::atomic<Node*> list_head(nullptr);
 
void append(int val)
{
    
    
    // append an element to the list
    Node* newNode = new Node{
    
    val, list_head};
 
    // next is the same as: list_head = newNode, but in a thread-safe way:
 
    while (!(list_head.compare_exchange_strong(newNode->next, newNode)));
    // (with newNode->next updated accordingly if some other thread just appended another node)
}
 
int main ()
{
    
    
    // spawn 10 threads to fill the linked list:
    std::vector<std::thread> threads;
    for (int i = 0; i < 10; ++i) 
        threads.push_back(std::thread(append, i));

    for (auto& th : threads) 
        th.join();
 
    // print contents:
    for (Node* it = list_head; it!=nullptr; it=it->next)
        std::cout << ' ' << it->value;
 
    std::cout << '\n';
 
    // cleanup:
    Node* it; 
    while (it=list_head) {
    
    
        list_head=it->next; 
        delete it;
    }
 
    return 0;
}

Supongo que te gusta

Origin blog.csdn.net/qq_24649627/article/details/114366823
Recomendado
Clasificación