调用std::call_once前检查initialized布尔变量是否安全?能否降低同步开销?
关于std::call_once前检查initialized变量的安全性与开销问题
安全性:你的写法不安全
直接读写普通bool类型的initialized会导致数据竞争,属于C++标准定义的未定义行为:
- 当一个线程在lambda中设置
initialized = true,其他线程读取这个变量时,没有任何同步机制约束,可能出现“看到initialized为true,但data还没完成初始化”的情况——编译器或CPU可能对指令重排序,导致初始化data的操作和设置initialized的操作在其他线程眼里顺序颠倒,最终触发assert失败或者读取到垃圾数据。
同步开销:你的做法没有意义,反而添乱
你想通过提前检查跳过std::call_once来降低开销,但实际上:
std::call_once本身已经做了优化:第一次调用会同步等待初始化完成,后续所有调用都会快速返回(内部已经维护了初始化完成的标记,不需要每次加锁等待)。- 你额外加的不安全检查不仅没降低开销,还引入了未定义行为,完全是画蛇添足。
正确的处理方式
方案1:直接用std::call_once(推荐)
去掉多余的initialized变量,完全依赖std::call_once的机制,代码更简洁安全:
struct Data { std::vector<int> data; std::once_flag flag; }; int run(Data& d, int l, int r) { std::call_once(d.flag, [&d]() { d.data.resize(5); std::generate(d.data.begin(), d.data.end(), [g = std::mt19937(std::random_device{}())]() mutable { return g() % 10; }); }); // read only ops: assert(d.data.size() == 5); return std::accumulate(d.data.cbegin() + l, d.data.cbegin() + r, 0); }
方案2:用原子变量做快速检查(仅在极端场景下考虑)
如果确实需要提前跳过std::call_once,必须把initialized改成原子变量,保证读写的同步性:
struct Data { std::vector<int> data; std::once_flag flag; std::atomic<bool> initialized = false; }; int run(Data& d, int l, int r) { if (!d.initialized.load(std::memory_order_acquire)) { std::call_once(d.flag, [&d]() { d.data.resize(5); std::generate(d.data.begin(), d.data.end(), [g = std::mt19937(std::random_device{}())]() mutable { return g() % 10; }); d.initialized.store(true, std::memory_order_release); }); } // read only ops: assert(d.data.size() == 5); return std::accumulate(d.data.cbegin() + l, d.data.cbegin() + r, 0); }
但要注意:这种方式的收益非常有限,因为std::call_once的后续调用已经足够快,额外的原子操作开销可能抵消掉所谓的性能提升,只有当run函数被极其频繁调用且初始化完成后调用量极大时,才值得考虑。
内容的提问来源于stack exchange,提问作者mian
相关产品推荐
相关产品推荐

