该用 std::latch 时用于一次性同步,如主线程等待所有工作线程完成;该用 std::barrier 时用于多轮循环同步,如并行迭代中每轮等待所有线程到达。

std::barrier 和 std::latch 是 C++20 引入的轻量级同步原语,专为“等待一组线程到达某个点”而设计。它们比 std::mutex + std::condition_variable 更简洁、更高效,且无所有权语义(不绑定线程),适合一次性或循环式协作场景。
什么时候该用 std::latch?
std::latch 是一次性计数器:初始化后只能递减(count_down())和等待(wait()),不可重置、不可重复使用。典型用于“主线程等所有工作线程完成”这类单次汇聚场景。
- 常见错误:在
latch已触发(count == 0)后再次调用count_down()—— 行为未定义;调用wait()后再调用count_down()也无效 - 使用场景:
std::thread启动多个任务,主线程用latch.wait()阻塞直到全部结束;或作为std::jthread的启动栅栏(配合std::stop_token初始化) - 性能影响:无锁 实现(通常基于原子操作),开销远低于条件变量;但不能复用,反复创建 / 销毁有分配成本
std::latch done(3); std::thread t1([&]{/* work */ done.count_down(); }); std::thread t2([&]{/* work */ done.count_down(); }); std::thread t3([&]{/* work */ done.count_down(); }); done.wait(); // 主线程阻塞,直到三次 count_down 完成 t1.join(); t2.join(); t3.join();
什么时候该用 std::barrier?
std::barrier 是可重用的同步点,每次所有参与线程调用 arrive()(或 arrive_and_wait())后自动重置计数,进入下一轮。适合多阶段并行计算,比如迭代算法中的每轮同步。
- 常见错误:混用
arrive()和arrive_and_wait()—— 若部分线程只调用arrive(),其余线程调用arrive_and_wait(),则可能死锁(前者不阻塞,后者会等全部到达) - 参数差异:
std::barrier构造时可传入 回调函数(std::barrier),该回调在每轮计数归零后、重置前由 ** 恰好一个 ** 到达线程执行(常用于更新共享状态或检查终止条件)b(n, []{/* once per phase */}); - 兼容性注意:MSVC 19.30+、GCC 11+、Clang 12+ 支持;旧版本需手动回退到
std::condition_variable
std::barrier sync(4); std::vector workers; for (int i = 0; i < 4; ++i) {workers.emplace_back([&sync]{for (int round = 0; round < 3; ++round) {// 每轮独立计算 do_work(round); sync.arrive_and_wait(); // 等其他 3 个线程也到达} }); } for (auto& t : workers) t.join();
为什么 不用 std::condition_variable 替代?
不是不能,而是容易出错且冗余。用条件变量模拟 latch 需要手动管理互斥量、计数器、通知逻辑;模拟 barrier 还得处理重入、唤醒丢失、虚假唤醒等问题。
立即学习“C++ 免费学习笔记(深入)”;
- 易踩的坑:
notify_one()写成notify_all()可能引发惊群;忘记在wait()前加while循环检查条件;互斥量粒度太粗导致串行化严重 - 性能差异:条件变量涉及内核态切换,
std::barrier/std::latch在多数情况下纯用户态完成(尤其计数未达阈值时仅原子操作) - 语义清晰性:
latch.wait()就是“等全部完成”,barrier.arrive_and_wait()就是“我到了,大家一起进下一轮”——意图直白,不易误用
实际协作中容易忽略的细节
两者都要求所有参与线程 ** 严格调用相同次数 ** 的同步操作,否则要么永远等待,要么未定义行为。没有运行时校验,编译器也不会报错。
-
std::latch的初始计数值必须等于预期调用count_down()的总次数;少一次 → 永不触发;多一次 → UB -
std::barrier的参与线程数在构造时固定,运行时增减线程需额外协调(例如用std::shared_ptr<:barrier>并配合引用计数) - 异常安全:若线程在到达前抛异常,未调用
arrive()或count_down(),整个同步将卡死——必须确保这些调用在try/catch或 RAII 包装器中完成






























