std::call_once 是 c++11 提供的线程安全机制,确保某函数全局仅执行一次;需配合静态或全局生命周期的 std::once_flag 使用,不可抛异常,且 flag 不能为局部变量。

std::call_once 是 C++11 引入的线程安全机制,用来确保某个函数(或可调用对象)在多线程环境下**全局只执行一次**。它不依赖手动加锁或标志位判断,底层由 std::once_flag 配合实现,是标准库提供的轻量、可靠方案。
为什么不能用普通 bool + std::mutex 手动控制?
看似可行,但容易出错:
- 双重检查锁定(Double-Checked Locking)在缺乏内存序约束时,可能因指令重排导致其他线程看到未完全构造的对象
- 忘记加锁、漏掉
std::lock_guard、或异常提前退出导致 flag 被设但函数未执行 - 多个
std::once_flag实例之间互不影响,而自定义 flag 容易误复用或作用域错误
std::call_once 的基本用法和参数要求
必须传入一个 std::once_flag& 和一个可调用对象(函数指针、lambda、functor 等),且该可调用对象不能抛异常(否则程序调用 std::terminate):
std::once_flag flag;
std::call_once(flag, []{
// 这段代码在所有线程中仅执行一次
init_global_resource();
});
常见错误:
立即学习“C++免费学习笔记(深入)”;
- 把
flag声明为局部变量(每次调用都新建)→ 失去“全局一次”语义 - 传入带异常的 lambda,如
[]{ throw std::runtime_error(""); }→ 程序直接终止 - 误用
std::call_once(flag, func, arg1, arg2)形式时,func若为成员函数,需用std::bind或 lambda 捕获,不能直接传&T::method
std::once_flag 必须是静态或全局生命周期
这是最容易被忽略的一点:std::once_flag 本身不可拷贝、不可移动,且内部维护状态;若其生命周期结束(比如函数内局部声明),则后续调用 std::call_once 行为未定义:
// ❌ 错误:flag 是局部变量,每次进入函数都重建
void unsafe_init() {
std::once_flag flag; // 每次调用都是新 flag
std::call_once(flag, do_init); // 总会执行!
}
// ✅ 正确:flag 必须 static 或全局
void safe_init() {
static std::once_flag flag;
std::call_once(flag, do_init);
}
补充说明:
- 多个线程同时调用同一
std::call_once(flag, ...),只有一个会真正执行可调用体,其余阻塞等待完成 -
std::once_flag可以是类的static成员,也可放在匿名命名空间里做文件级单例控制 - 它不保证执行时机(谁先抢到谁触发),只保证结果唯一性;不适合用于依赖严格初始化顺序的场景
真正要注意的是:flag 的生存期比调用点更长,且不能跨 DLL 边界共享(Windows 下不同模块的 std::once_flag 实例不互通)。如果初始化逻辑复杂或涉及跨模块协作,得额外设计同步契约,而不是只靠 std::call_once 一招鲜。











