std::barrier 更轻量因其无需手动管理等待/唤醒,采用原子计数或自旋+内核通知混合策略,避免虚假唤醒、锁竞争和上下文切换;它仅支持对称同步(所有线程均需调用 arrive() 一次),且初始化时 expected 值必须与实际线程数严格一致。

std::barrier 为什么比 std::mutex + condition_variable 更轻量?
因为 std::barrier 不需要手动管理等待状态和唤醒逻辑,底层用原子计数 + 无锁路径(部分实现)或高效自旋+内核通知混合策略,避免了条件变量常见的虚假唤醒、锁竞争和上下文切换开销。它专为「所有线程到达后统一放行」这一模式设计,没有 owner 线程概念,也不支持单次唤醒。
常见错误是试图用 std::barrier 实现「某一个线程先到、另一个稍后才等」的非对称同步——这不行,它只保证「第 N 次 arrive() 调用触发 completion phase,然后所有线程继续」。
如何正确初始化并在线程中调用 arrive()?
必须确保构造时传入的预期线程数(expected)与实际参与同步的线程数严格一致;少一个,屏障永远不触发;多一个,arrive() 会抛出 std::system_error(错误码为 std::errc::invalid_argument)。
-
std::barrier b{4};表示等 4 个线程都调用arrive()才放行 - 每个线程必须且仅调用一次
b.arrive()(除非你用的是带返回值的重载,见下一点) - 若需在 completion phase 做清理或广播操作,用带回调的构造函数:
std::barrier b{4, []{ /* run once, in one thread */ }};
completion phase 回调里能做什么?
回调函数由其中一个成功到达的线程执行(具体哪个不确定),执行期间其他线程仍在阻塞等待。这意味着:
- 回调内不能依赖其它线程已退出或已修改共享状态(它们还没离开 barrier)
- 回调不可抛异常,否则程序直接调用
std::terminate() - 适合做轻量级全局操作:比如递增轮次计数器、刷新某个标志位、或向外部系统发一次心跳
- 不要在回调里调用
b.arrive()或任何可能再次进入该 barrier 的逻辑——会死锁
示例:std::barrier b{3, []{ std::cout —— 输出只会出现一次,且发生在三个线程都抵达之后、它们各自继续执行之前。
std::barrier 和 std::latch 的关键区别在哪?
最常混淆的点:两者都做同步,但语义完全不同。
-
std::latch是一次性门闩(count-down only),count_down()后不可重用;std::barrier可重复使用,每次arrive()都构成一轮同步 -
std::latch允许不同线程调用多次count_down()(只要总和达标),而std::barrier要求每个参与者严格调用一次arrive()(除非用arrive(n)显式指定权重) - 如果你需要「多个生产者往队列写,等全部写完再通知消费者」,用
std::latch;如果是「每轮迭代所有 worker 处理完局部数据,再一起进下一轮」,就该用std::barrier
容易被忽略的是:barrier 的可重入性依赖于内部状态自动重置,但如果你在 completion phase 回调里意外修改了 barrier 对象本身(比如移动赋值),后续调用行为未定义。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











