std::latch是一次性同步原语,不可重用;std::barrier支持多轮同步且可配置回调函数,但回调不得抛异常。两者参与线程数均在构造时固定,不可动态调整。

std::latch 和 std::barrier 都只能触发一次吗?
不是。std::latch 是一次性同步点:构造时指定计数 count,每次调用 count_down() 减一,减到 0 后所有等待线程被唤醒,之后再调用 wait() 会立即返回,count_down() 也不再有效 —— 它不可重用。std::barrier 则默认支持重复使用:每次所有参与线程调用 arrive_and_wait()(或配对的 arrive() + wait())后,计数重置为初始值,可进入下一轮同步。
常见错误现象:误把 std::latch 当作循环屏障用,在 for 循环里反复 wait(),结果第二轮起直接“穿透”,线程不同步。
- 若需单次汇聚(如:主线程等所有工作线程初始化完成),用
std::latch - 若需多轮协同(如:并行计算的每一轮迭代都需全体就绪),必须用
std::barrier -
std::barrier构造时可传入一个回调函数(std::function),在每轮计数归零、唤醒前执行 ——std::latch没有该能力
它们的 arrive() / count_down() 行为有何关键差异?
std::latch::count_down() 只是减计数,不阻塞;std::barrier::arrive() 也是非阻塞到达,但返回一个 std::barrier::arrival_token,配合 wait() 才构成“到达并等待”。而 std::barrier::arrive_and_wait() 是原子操作:它既完成到达,又阻塞直到本轮所有线程都到达。
容易踩的坑:
立即学习“C++免费学习笔记(深入)”;
- 对
std::latch调用count_down()多次,可能提前触发唤醒,且无法撤回 - 对
std::barrier混用arrive()和arrive_and_wait():比如线程 A 调用arrive(),线程 B 调用arrive_and_wait(),B 会等 A 再调一次wait()—— 但 A 并没调,导致死锁 -
std::barrier的arrive()返回的 token 必须被某个线程用于wait(),否则该轮无法完成(除非所有线程都用arrive_and_wait())
性能和内存开销谁更轻量?
std::latch 实现通常更简单:内部只需一个原子计数器 + 一个 futex 或条件变量。C++20 标准允许其实现为无锁(lock-free)结构,多数编译器(如 libstdc++、libc++)对其做了优化。
std::barrier 需维护轮次状态、支持回调、保证重入安全,实现更复杂;某些实现(如早期 MSVC)甚至基于互斥量模拟,存在额外开销。
使用场景建议:
- 启动阶段一次性同步(例如:N 个线程初始化完毕才开始主逻辑),优先选
std::latch,更快更省 - 需要动态调整参与线程数?注意:
std::latch和std::barrier的参与数都在构造时固定,运行时不能增减;真有此需求,得自己封装或换用std::condition_variable - 高吞吐循环中频繁同步(如每微秒一轮),应压测两种类型的实际延迟,不要只看理论
为什么 std::barrier 的回调函数不能抛异常?
标准明确要求:std::barrier 的回调函数(如果提供)**不得抛出异常**,否则调用 std::terminate。这是因为回调发生在所有线程已到达、即将集体唤醒的临界路径上,没有安全的异常传播上下文 —— 无法判断由哪个线程处理、也不能让部分线程继续而部分终止。
实际写法中容易忽略这点:
- 回调里调用了可能抛异常的函数(如
std::vector::push_back()在内存不足时可能抛std::bad_alloc) - 回调中用了带异常规格的 lambda,但没加
noexcept - 正确做法:回调内所有操作必须是
noexcept的,或手动捕获并记录错误(如写日志),绝不能让异常逃逸
相比之下,std::latch 根本不提供回调机制,自然没这问题 —— 但也意味着你得在外层手动安排“唤醒后要做的事”。










