该用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(); });
<p>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<t> b(n, []{ /* once per phase */ });</t>),该回调在每轮计数归零后、重置前由**恰好一个**到达线程执行(常用于更新共享状态或检查终止条件) - 兼容性注意:MSVC 19.30+、GCC 11+、Clang 12+ 支持;旧版本需手动回退到
std::condition_variable
std::barrier sync(4);
std::vector<std::thread> 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></:barrier>并配合引用计数) - 异常安全:若线程在到达前抛异常,未调用
arrive()或count_down(),整个同步将卡死——必须确保这些调用在try/catch或 RAII 包装器中完成










