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

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
*/
