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

Este artículo presenta el tipo atómico más simple en el archivo de encabezado <atomic>: atomic_flag. atomic_flag Un tipo booleano atómico simple que solo admite dos operaciones, test-and-set y clear.

std :: constructor atomic_flag

El constructor std :: atomic_flag es el siguiente:

  • atomic_flag () noexcept = predeterminado;
  • atomic_flag (const atomic_flag & T) = eliminar;

std :: atomic_flag solo tiene el constructor predeterminado, y el constructor de copia ha sido deshabilitado, por lo que no se puede construir un nuevo objeto std :: atomic_flag a partir de otros objetos std :: atomic_flag.

Si la inicialización ATOMIC_FLAG_INIT no se usa explícitamente durante la inicialización, entonces el estado del objeto std :: atomic_flag recién creado no está especificado (ni establecido ni borrado). Además, atomic_flag no se puede copiar ni mover.

ATOMIC_FLAG_INIT: Si un objeto std :: atomic_flag se inicializa con esta macro, se puede garantizar que el objeto std :: atomic_flag está en el estado claro cuando se crea.

Veamos un ejemplo simple. La función main () crea 10 subprocesos para el conteo. El subproceso que completa la tarea de conteo primero genera su propia ID, y el subproceso que completa la tarea de conteo posteriormente no genera su propia ID:

#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

std::atomic<bool> ready(false);                // can be checked without being set
std::atomic_flag winner = ATOMIC_FLAG_INIT;    // always set when checked

void count1m(int id)
{
    
    
    while (!ready) {
    
    
        std::this_thread::yield();
    } // 等待主线程中设置 ready 为 true.

    for (int i = 0; i < 1000000; ++i) {
    
    
    } // 计数.

    // 如果某个线程率先执行完上面的计数过程,则输出自己的 ID.
    // 此后其他线程执行 test_and_set 是 if 语句判断为 false,
    // 因此不会输出自身 ID.
    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(count1m, i));
        
    ready = true;

    for (auto & th:threads)
        th.join();

    return 0;
}

Introducción a std :: atomic_flag :: test_and_set

El prototipo de la función test_and_set de std :: atomic_flag es el siguiente:

bool test_and_set (memory_order sync = memory_order_seq_cst) volatile noexcept;
bool test_and_set (memory_order sync = memory_order_seq_cst) noexcept;

La función test_and_set () verifica el indicador std :: atomic_flag. Si std :: atomic_flag no se ha establecido antes, establece el indicador std :: atomic_flag y devuelve si se ha establecido el objeto std :: atomic_flag anterior, si std: : Si se ha establecido el objeto atomic_flag, devuelve verdadero; de lo contrario, devuelve falso.

La operación de prueba y configuración es atómica (por lo que prueba y configuración es una operación atómica de lectura, modificación y escritura (RMW)).

test_and_set puede especificar el orden de memoria (los siguientes artículos presentarán el orden de memoria de C ++ 11 en detalle, aquí está el valor de sincronización del parámetro test_and_set para completar), el valor es el siguiente:
Inserte la descripción de la imagen aquí
un ejemplo simple:

#include <iostream>               // std::cout
#include <atomic>                 // std::atomic_flag
#include <thread>                 // std::thread
#include <vector>                 // std::vector
#include <sstream>                // std::stringstream

std::atomic_flag lock_stream = ATOMIC_FLAG_INIT;
std::stringstream stream;

void append_number(int x)
{
    
    
    while (lock_stream.test_and_set()) {
    
    
    }
    stream << "thread #" << x << '\n';
    lock_stream.clear();
}

int main()
{
    
    
    std::vector<std::thread> threads;
    for (int i = 1; i <= 10; ++i)
        threads.push_back(std::thread(append_number, i));
        
    for (auto & th:threads)
        th.join();

    std::cout << stream.str() << std::endl;;
    return 0;
}

std :: atomic_flag :: clear () introducción

Excepto por el bit de bandera del objeto std :: atomic_flag, es decir, establezca el valor de atomic_flag en falso. El prototipo de la función clara es el siguiente:

void clear (memory_order sync = memory_order_seq_cst) volatile noexcept;
void clear (memory_order sync = memory_order_seq_cst) noexcept;

Borrar el indicador std :: atomic_flag hace que la siguiente llamada a std :: atomic_flag :: test_and_set devuelva falso.
std :: atomic_flag :: clear () puede especificar el orden de memoria, con valores como se muestra en la tabla anterior.

Combinando std :: atomic_flag :: test_and_set () y std :: atomic_flag :: clear (), el objeto std :: atomic_flag se puede usar como un bloqueo de giro simple, consulte el siguiente ejemplo:

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

std::atomic_flag lock = ATOMIC_FLAG_INIT;

void f(int n)
{
    
    
    for (int cnt = 0; cnt < 100; ++cnt) {
    
    
        while (lock.test_and_set(std::memory_order_acquire))  // acquire lock
             ; // spin
        std::cout << "Output from thread " << n << '\n';
        lock.clear(std::memory_order_release);               // release lock
    }
}

int main()
{
    
    
    std::vector<std::thread> v;
    for (int n = 0; n < 10; ++n) {
    
    
        v.emplace_back(f, n);
    }
    for (auto& t : v) {
    
    
        t.join();
    }
}

En el programa anterior, la operación de bloqueo del bloqueo del objeto std :: atomic_flag puede entenderse como lock.test_and_set (std :: memory_order_acquire); y la operación de desbloqueo es equivalente a lock.clear (std :: memory_order_release).

Al bloquear, si lock.test_and_set devuelve falso, significa que el bloqueo es exitoso (mientras que no entrará en el estado de giro en este momento), porque el bit de bandera del bloqueo es falso antes (es decir, ningún subproceso está bloqueando el bloqueo) , Pero después de llamar a test_and_set, el indicador de bloqueo es verdadero, lo que indica que cierto hilo ha obtenido el bloqueo con éxito.

Si otro hilo también llama a lock.test_and_set (std :: memory_order_acquire) antes de que se desbloquee el hilo (es decir, llama a lock.clear (std :: memory_order_release)) para intentar adquirir el bloqueo, entonces test_and_set (std :: memory_order_acquire) regresa true, luego while entra en el estado de giro. Si el hilo que adquirió el bloqueo está desbloqueado (es decir, después de llamar a lock.clear (std :: memory_order_release)), un hilo intenta llamar a lock.test_and_set (std :: memory_order_acquire) y devuelve falso, entonces el while no entrará El giro Indica que el hilo adquirió correctamente el bloqueo.

De acuerdo con el análisis anterior, sabemos que el objeto std :: atomic_flag se puede utilizar como un simple bloqueo de giro en determinadas circunstancias.

Supongo que te gusta

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