std::barrier是C++20引入的循环同步原语,适用于多线程分阶段协作场景,如并行计算的每轮迭代同步;它支持自动重置,需精确指定并确保恰好N个线程调用wait()。

std::barrier 是什么,适合在哪种同步场景用
std::barrier 是 C++20 引入的线程同步原语,专为「多个线程在某个点集体等待、全部到达后才一起继续」这种集合点(rendezvous)场景设计。它比 std::condition_variable + std::mutex 更轻量,也比反复轮询或 std::latch(仅能触发一次)更贴合循环协作模式。
典型适用场景:多线程并行计算中需要分阶段执行,比如每轮迭代开始前所有线程必须完成上一轮工作;或模拟物理仿真中每个时间步所有线程更新完状态再统一推进。
如何正确构造和调用 barrier.wait()
构造时需指定预期到达的线程数(expected),该值不可变。每次调用 barrier.wait() 会阻塞当前线程,直到第 expected 个线程抵达 —— 此时所有等待线程被同时唤醒,且 barrier 自动重置为下一轮等待(这是它和 std::latch 的关键区别)。
- 必须确保**恰好**有
expected个线程调用wait(),少一个会永久挂起,多一个会触发未定义行为(通常 crash 或死锁) - 同一个
std::barrier对象可被重复使用,无需重建 - 不支持超时等待(C++20 标准中无
try_wait_for等变体),如需超时需自行封装
std::barrier b{4}; // 期待 4 个线程到达
std::vector threads;
for (int i = 0; i < 4; ++i) {
threads.emplace_back([&b, i] {
// 每个线程做些事
do_work(i);
// 到达集合点
b.wait(); // 阻塞直到第 4 个线程也调用 wait()
// 所有线程在此处之后并发继续
continue_processing(i);
});
}
for (auto& t : threads) t.join(); 为什么不能用 std::latch 替代 barrier 实现循环同步
std::latch 是一次性门闩:构造时设 count,每次 count_down() 减一,减到零后所有等待者被唤醒,但之后再调用 wait() 会立即返回,无法重置。而 std::barrier 在每次全员到达后自动归零并重置计数器,天然适配多轮同步。
- 若强行用
std::latch做多轮同步,你得在每轮开始前 new 一个新 latch,或手动管理生命周期,极易出错 -
std::barrier的arrive()成员函数允许提前通知到达(不阻塞),配合wait()可实现更灵活的协调逻辑(例如某些线程只报告不等待) - 底层实现上,
barrier通常比反复构造latch更高效,避免内存分配和销毁开销
常见误用与崩溃原因
实际写代码时最容易栽在生命周期和调用次数上:
-
std::barrier对象必须**在线程调用wait()期间持续有效** —— 如果它是个局部变量,而主线程在子线程还在wait()时就退出了作用域,会导致未定义行为(常见 SIGSEGV) - 误把
barrier.arrive()当作wait()使用:前者只递减计数并返回新计数值,不阻塞;若只调arrive()不调wait(),其他线程会在wait()处永远等不到第expected个到达 - 跨线程传递 barrier 时用了裸指针或引用,但源对象已析构,尤其在 lambda 捕获时容易忽略捕获方式:
[&b]比[b]安全,但更要确保b的生存期覆盖所有线程
复杂点在于:barrier 的语义干净,但它的正确性完全依赖程序员对线程数量和生命周期的手动保证,编译器和运行时几乎不帮你检查。稍不留神,就是静默死锁或随机崩溃。










