Daily bit(e) of C++. std::call_once

Добавлено 18 июля 2026 в 17:25

Daily bit(e) C++ 33, гарантирование единственного вызова вызываемой функции из нескольких конкурентных вызывающих функций с помощью std::call_once.

Daily bit(e) C++

std::call_once – это низкоуровневый инструмент синхронизации, гарантирующий единственный успешный вызов (т.е. вызов, который не генерирует исключение) вызываемой функции.

Каждая попытка вызова (с тем же флагом) синхронизируется с завершением предыдущей попытки, а завершение успешного вызова синхронизируется со всеми конкурентными вызовами, делая любые изменения состояния видимыми для всех потоков без дополнительной синхронизации.

Обратите внимание, что если синхронизация локализована в одной функции, локальная статическая переменная обеспечивает те же гарантии для ее инициализации. std::call_once можно использовать для синхронизации между несколькими функциями.

#include <mutex>
#include <thread>
#include <vector>
#include <numeric>
#include <print>

namespace {
std::vector<int> data;
std::once_flag flag;

// операция, которая дважды завершится неудачей, а потом успехом
void read_data() 
{
    static int fail_count = 2;
    if (fail_count != 0) 
    {
        --fail_count;
        throw std::runtime_error{"Requested to fail"};
    }
    data = {1,2,3,4,5};
}
}

int sum() 
{
    // вызвать read_data() один раз для всех вызовов, используя флаг
    std::call_once(flag, read_data);
    // дополнительно мы можем передать аргументы вызываемой функции:
    // std::call_once(flag, read_data, arg1, arg2, ...);
    return std::accumulate(data.cbegin(), data.cend(),
                           0, std::plus<>{});
}

int product() 
{
    std::call_once(flag, read_data);
    return std::accumulate(data.cbegin(), data.cend(),
                           1, std::multiplies<>{});
}


// Запуск параллельно нескольких экземпляров sum() и product()
std::vector<std::jthread> runners;
std::generate_n(std::back_inserter(runners), 4, []{
    return std::jthread([]{
        try 
        {
            std::println("sum == {}", sum());
        }
        catch(const std::exception& e) 
        {
            std::println("failed to calculate sum: {}", e.what());
        }
    });
});
std::generate_n(std::back_inserter(runners), 4, []{
    return std::jthread([]{
        try 
        {
            std::println("product == {}", product());
        }
        catch(const std::exception& e) 
        {
            std::println("failed to calculate product: {}", e.what());
        }
    });
});

/* Пример вывода:
failed to calculate sum: Requested to fail
failed to calculate sum: Requested to fail
sum == 15
sum == 15
product == 120
product == 120
product == 120
product == 120
*/

Пример на Compiler Explorer

Теги

C++ / CppDaily bit(e) C++МногопоточностьПрограммирование