Daily bit(e) C++ #33, Низкоуровневый инструмент одиночного вызова: std::call_once

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

Все последующие попытки (даже сделанные во время одиночного вызова) будут синхронизированы с выходом вызываемого объекта, а это означает, что любые изменения, сделанные во время вызова, будут видны всем потокам.

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

#include <memory>
#include <mutex>
#include <thread>
#include <vector>
#include <algorithm>

constexpr inline std::string_view resource_path = "/some/path";

struct GlobalResource {
    struct Header{};
    struct Body{};
    static const Header& header() {
        std::call_once(flag_, load_resource, resource_path);
        return instance_->header_;
    }
    static const Body& body() {
        std::call_once(flag_, load_resource, resource_path);
        return instance_->body_;
    }
private:
    Header header_;
    Body body_;
    static void load_resource(std::string_view path) {
        auto res = std::make_unique<GlobalResource>();
        instance_ = std::move(res);
    }
    static std::once_flag flag_;
    static std::unique_ptr<GlobalResource> instance_;
};

std::unique_ptr<GlobalResource> GlobalResource::instance_ = nullptr;
std::once_flag GlobalResource::flag_;

int main() {
    std::vector<std::jthread> runners;
    // spawn 4 threads
    std::generate_n(std::back_inserter(runners), 4,[]{
        return std::jthread([]{
            auto& header = GlobalResource::header();
            // process header
            auto& body = GlobalResource::body();
            // process body
        });
    });
}

Откройте этот пример в Compiler Explorer.