std::barrier在C++20中需用正整数阈值初始化(如std::barrier{4}),线程调用arrive_and_wait()阻塞等待全员到达;它不可拷贝或移动,因语义要求生命周期严格绑定共享作用域,须通过引用或指针传递。

std::barrier 在 C++20 中怎么初始化和等待
std::barrier 是 C++20 引入的轻量级线程同步原语,用于让一组线程在某个点集体等待,直到全部到达才一起继续。它比 std::latch 更适合循环场景(比如多轮并行计算),因为可重复使用。
初始化时必须指定参与线程数(即“阈值”),且不能为 0:
std::barrier barrier{4}; // 4 个线程共同等待
每个线程调用 arrive_and_wait() 进入屏障点;该函数会阻塞,直到所有线程都调用过一次(或更多次,取决于 arrive 次数):
- 若只调用
arrive_and_wait(),等价于“抵达 + 等待”,内部自动计数 +1 - 若需提前抵达(比如某线程工作更快),可用
arrive()单独计数,之后再调用wait() -
arrive()返回std::barrier::arrival_token,但一般无需手动管理,除非要定制到达逻辑
为什么 std::barrier 不支持 move 和 copy
std::barrier 的拷贝构造、移动构造、赋值运算符都被显式删除了 —— 它是不可复制、不可移动的类型。这不是设计疏漏,而是语义要求:屏障对象代表一个共享同步点,其生命周期必须严格绑定到所有参与线程都能访问的作用域中。
立即学习“C++免费学习笔记(深入)”;
常见误用:
- 把
std::barrier作为参数传给线程函数(未用引用或指针)→ 编译失败:use of deleted function 'std::barrier<...>::barrier(const std::barrier<...>&)'</...></...> - 试图
std::move到 lambda 捕获列表 → 同样触发删除函数报错
正确做法:用指针或引用传递,例如:
std::barrier barrier{num_threads};
for (int i = 0; i < num_threads; ++i) {
threads.emplace_back([&barrier, i] {
do_work(i);
barrier.arrive_and_wait();
});
}
std::barrier 的 completion phase 怎么用
std::barrier 支持可选的 completion phase 函数,在每次所有线程抵达后、释放等待前执行。这个函数由**其中一个线程**(不指定是哪一个)同步调用,常用于做聚合操作或状态更新。
注意点:
- completion function 必须是无参、无返回值的可调用对象(如 lambda、函数指针)
- 它运行在线程上下文中,不能抛异常(否则调用
std::terminate) - 它与 barrier 的生命周期强绑定:如果 barrier 被销毁时还有线程在
wait(),行为未定义
示例(每轮结束后重置计数器):
int global_counter = 0;
std::barrier barrier{4, []{ global_counter = 0; }};
// 所有线程调用 arrive_and_wait() 后,恰好一个线程会执行 global_counter = 0
std::barrier 和 std::latch、std::semaphore 的关键区别
三者都用于线程同步,但语义和适用场景不同:
-
std::latch:一次性计数器,count_down()递减,wait()阻塞直到归零;不可重用,适合“启动信号”或“结束等待” -
std::barrier:循环计数器,每次全员抵达后自动重置;适合多轮并行迭代(如数值模拟的 time step) -
std::semaphore:通用资源计数,支持任意增减(acquire()/release());但没有“全体就绪”的语义,也不保证唤醒顺序
性能上,std::barrier 实现通常基于 futex 或类似内核机制,在 Linux 上能避免不必要的用户态调度开销;但若只用一次,std::latch 更轻量。
容易忽略的一点:completion function 的执行时机是在“所有线程已抵达、但尚未被唤醒”之间,此时其他线程仍处于阻塞状态 —— 所以它适合做原子状态切换,但不适合做耗时操作(会拖慢整个屏障释放)。



















