C++中的std::call_once是什么?C++线程安全的单次初始化【多线程】

std::call_once 是 C++11 提供的线程安全机制,确保某函数在多线程下仅执行一次,需配合 std::once_flag 使用;flag 必为静态生命周期,异常不标记完成,性能优于手动 mutex,语义清晰且异常安全。

std::call_once 是 C++11 引入的线程安全工具,用于确保某个函数(或可调用对象)在多线程环境下**只被执行一次**,即使多个线程同时调用它。它常和 std::once_flag 配合使用,是实现线程安全单次初始化(如单例、资源首次加载、全局状态设置等)的核心机制。

核心组成:std::once_flag + std::call_once

必须成对使用:

  • std::once_flag 是一个不可复制、不可移动的标记对象,用来记录“是否已执行过”;每个需要单次初始化的逻辑应有自己独立的 static 或全局 std::once_flag 实例。
  • std::call_once(flag, func, ...) 接收一个 once_flag& 和一个可调用对象(支持函数指针、lambda、bind、成员函数等),以及可选参数。它会阻塞其他竞争线程,直到该可调用对象成功执行完毕——且仅有一个线程真正执行,其余线程等待后直接返回。

典型用法示例:延迟初始化全局资源

比如加载配置、创建线例、初始化日志系统:

std::shared_ptr g_config;
std::once_flag g_config_init_flag;

void init_config() {
    g_config = std::make_shared("config.json");
}

// 多个线程可能同时调用这个函数
std::shared_ptr get_config() {
    std::call_once(g_config_init_flag, init_config);
    return g_config;
}

无论多少线程并发调用 get_config()init_config() 只运行一次,g_config 初始化安全无竞态。

立即学习“C++免费学习笔记(深入)”;

关键注意事项

  • std::once_flag 必须是 static 或具有静态生命周期(如全局、类 static 成员),否则每次调用都新建 flag,失去“一次”的语义。
  • 若传入的函数抛出异常,std::call_once 会捕获并重新传播给当前线程;但该次调用被视为“失败”,flag 不置位——下次调用仍会尝试执行(即:异常 ≠ 已完成)。
  • 内部基于原子操作和轻量级锁实现,开销极小,远低于手写 double-checked locking 或 mutex 全局保护。
  • 不适用于需要“首次调用即初始化 + 后续调用需等待完成”的场景?其实它就是干这个的——所有线程都会等到那个唯一成功执行的线程完成才继续。

和 std::mutex 对比:为什么更推荐 call_once?

相比手动用 mutex 加锁做单次检查:

  • 代码更简洁,语义更明确:“我要确保这事只干一次”;
  • 无需担心忘记 unlock、死锁、异常安全(lock_guard 自动释放)等问题;
  • 标准库可针对平台优化(如 Linux 下可能用 futex),性能通常更好;
  • 天然支持函数对象和参数转发,灵活度高。

基本上就这些。用好 std::call_once,能帮你避开大量多线程初始化的坑。