std::call_once 保证函数在多线程下仅执行一次,需配合静态或全局的 std::once_flag 使用,被调函数必须 noexcept,否则触发 std::terminate;相比手动标志+mutex,它原子性强、无竞态且更轻量。
它本身不执行函数,而是配合 std::once_flag 控制执行时机:多个线程同时调用 std::call_once(flag, func),最终只有其中一个线程会真正运行 func,其余线程会阻塞直到 func 完成,然后全部继续向下执行。
如果 std::once_flag 是局部变量(比如函数内定义),每次调用函数都会新建一个 flag,就完全失去“只执行一次”的意义;更严重的是,它的内部状态依赖静态存储期,局部对象可能引发未定义行为。
static std::once_flag flag; 或全局/类静态成员std::once_flag flag;(栈上临时对象)std::once_flag 不可拷贝、不可移动,也不能手动赋值std::call_once 内部实现要求被调函数必须 noexcept。如果 func 抛出异常,C++ 标准规定调用 std::terminate() —— 不是捕获、不是重试,而是直接结束进程。
try/catch 吞掉异常,或确保逻辑无异常路径std::thread 构造、new 失败、文件打开失败等static std::once_flag flag;
std::call_once(flag, []() noexcept {
try {
// 可能出错的初始化逻辑
init
_resource();
} catch (...) {
// 记录日志或设置错误状态,但绝不让异常逃出
g_init_failed = true;
}
});
手动实现“只执行一次”容易写出竞态:比如先判断 if (!inited) { inited = true; do_init(); },其中赋值和调用之间存在窗口,两个线程都可能进入。而 std::call_once 是原子性地检查+标记+执行,由标准库在底层用平台原语(如 futex、SRWLock)高效实现。
std::once_flag 的生命周期约束和异常安全性——这两点一旦出错,问题往往延迟暴露,调试成本远高于加锁逻辑本身。