Руководство по параллелизму C ++ std :: atomic_flag

В этой статье представлен простейший атомарный тип в заголовочном файле <atomic>: atomic_flag. atomic_flag Простой атомарный логический тип, который поддерживает только две операции: test-and-set и clear.

std :: atomic_flag конструктор

Конструктор std :: atomic_flag выглядит следующим образом:

  • atomic_flag () noexcept = по умолчанию;
  • atomic_flag (const atomic_flag & T) = удалить;

std :: atomic_flag имеет только конструктор по умолчанию, а конструктор копирования был отключен, поэтому новый объект std :: atomic_flag не может быть построен из других объектов std :: atomic_flag.

Если инициализация ATOMIC_FLAG_INIT не используется явно во время инициализации, то состояние вновь созданного объекта std :: atomic_flag не указано (ни установлено, ни очищено). Кроме того, atomic_flag не может быть скопирован или перемещен.

ATOMIC_FLAG_INIT: если объект std :: atomic_flag инициализируется этим макросом, можно гарантировать, что объект std :: atomic_flag находится в чистом состоянии при его создании.

Давайте посмотрим на простой пример. Функция main () создает 10 потоков для подсчета. Поток, который завершает задачу подсчета, сначала выводит свой собственный идентификатор, а поток, который завершает задачу подсчета, впоследствии не выводит свой собственный идентификатор:

#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;
}

Введение в std :: atomic_flag :: test_and_set

Прототип функции test_and_set для std :: atomic_flag выглядит следующим образом:

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;

Функция test_and_set () проверяет флаг std :: atomic_flag. Если std :: atomic_flag не был установлен ранее, она устанавливает флаг std :: atomic_flag и возвращает, был ли установлен предыдущий объект std :: atomic_flag, если std: : Если объект atomic_flag был установлен, он возвращает true, в противном случае возвращает false.

Операция test-and-set является атомарной (так что test-and-set - это атомарная операция чтения-изменения-записи (RMW)).

test_and_set может указывать порядок памяти (следующие статьи подробно расскажут о порядке памяти C ++ 11, здесь значение параметра test_and_set sync для полноты), значение выглядит следующим образом:
Вставьте описание изображения сюда
простой пример:

#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 () введение

За исключением бита флага объекта std :: atomic_flag, то есть установите для atomic_flag значение false. Прототип функции clear выглядит следующим образом:

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

Очистка флага std :: atomic_flag приводит к тому, что следующий вызов std :: atomic_flag :: test_and_set возвращает false.
std :: atomic_flag :: clear () может указывать порядок памяти со значениями, как показано в таблице выше.

Комбинируя std :: atomic_flag :: test_and_set () и std :: atomic_flag :: clear (), объект std :: atomic_flag можно использовать как простую спин-блокировку, см. Следующий пример:

#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();
    }
}

В приведенной выше программе операцию блокировки объекта std :: atomic_flag можно понять как lock.test_and_set (std :: memory_order_acquire); а операция разблокировки эквивалентна lock.clear (std :: memory_order_release).

При блокировке, если lock.test_and_set возвращает false, это означает, что блокировка успешна (пока не войдет в состояние вращения в это время), потому что бит флага блокировки раньше был ложным (то есть ни один поток не блокировал блокировку) , Но после вызова test_and_set флаг блокировки становится истинным, указывая, что определенный поток успешно получил блокировку.

Если другой поток также вызывает lock.test_and_set (std :: memory_order_acquire) до того, как поток будет разблокирован (то есть вызывает lock.clear (std :: memory_order_release)), чтобы попытаться получить блокировку, тогда test_and_set (std :: memory_order_acquire) возвращает истина, затем в то время как переходит в состояние вращения. Если поток, получивший блокировку, разблокирован (то есть после вызова lock.clear (std :: memory_order_release)), поток пытается вызвать lock.test_and_set (std :: memory_order_acquire) и возвращает false, тогда while не войдет спин. Указывает, что поток успешно получил блокировку.

Согласно приведенному выше анализу мы знаем, что объект std :: atomic_flag может использоваться в качестве простой спин-блокировки при определенных обстоятельствах.

рекомендация

отblog.csdn.net/qq_24649627/article/details/114364317