在 C++20 标准中,<barrier> 头文件引入了一个全新的同步原语——std::barrier。它与传统的 std::mutex、std::condition_variable 或 std::latch 相比,提供了更简洁且高效的“协程”式同步方式。本文将从设计理念、典型使用场景以及性能优势等角度,解释为什么 std::barrier 成为现代多线程编程的首选工具。
1. 设计理念
std::barrier 的核心思路是“等待所有参与者到达同一点,然后一次性释放”。与 latch 只在计数为 0 时一次性触发不同,barrier 允许在每次循环后重新复用。它本质上是一个可重复使用的计数器,支持“每轮任务”同步。
std::barrier sync(4); // 需要 4 个线程参与
每个线程在执行完一轮任务后调用 arrive_and_wait(),当计数器归零时,所有线程被唤醒,计数器自动重置,准备下一轮同步。
2. 典型使用场景
| 场景 | 传统实现 | 采用 barrier 的实现 |
|---|---|---|
| 数据并行 | 线程间使用 condition_variable 或 latch 逐轮同步 |
std::barrier 一行即可完成多次同步 |
| 管道式处理 | 手写锁、信号量 | 直接使用 barrier 控制管道的各阶段 |
| 模拟多轮游戏 | 每轮结束手动管理计数器 | barrier 内置计数重置,减少错误 |
示例:并行归并排序
void parallel_merge_sort(std::vector <int>& data, int depth, std::barrier& sync) {
if (depth == 0) {
std::sort(data.begin(), data.end());
return;
}
int mid = data.size() / 2;
std::thread left([&]{
std::vector <int> left_part(data.begin(), data.begin() + mid);
parallel_merge_sort(left_part, depth-1, sync);
data.begin() = std::move(left_part.begin());
});
std::thread right([&]{
std::vector <int> right_part(data.begin() + mid, data.end());
parallel_merge_sort(right_part, depth-1, sync);
data.begin() = std::move(right_part.begin());
});
left.join();
right.join();
std::inplace_merge(data.begin(), data.begin() + mid, data.end());
sync.arrive_and_wait(); // 所有线程同步
}
在每个递归层级,线程都会等待同层的其他线程完成归并,保证数据完整性。
3. 性能优势
| 对比 | std::condition_variable |
std::latch |
std::barrier |
|---|---|---|---|
| 复用性 | 必须手动重置 | 只能触发一次 | 自动重置 |
| 上下文切换 | 线程阻塞后恢复 | 同上 | 轻量级原子操作 |
| 调度开销 | 由线程调度器决定 | 由调度器决定 | 只涉及原子计数,极低开销 |
| 使用简洁性 | 需要额外条件判断 | 需要手动检查计数 | 单行 arrive_and_wait() |
在多核 CPU 上,barrier 的原子计数器操作占用的时间远低于传统的互斥锁+条件变量组合,尤其在任务粒度较小、同步频繁的场景中,收益尤为明显。
4. 与 C++20 其他同步原语的比较
| 原语 | 适用场景 | 主要差异 |
|---|---|---|
std::latch |
只在计数归零后触发一次 | barrier 可复用 |
std::future / std::promise |
线程间单次数据传递 | barrier 专注同步,不涉及数据 |
std::atomic_flag |
轻量级“自旋锁” | barrier 更高层次的同步逻辑 |
5. 结语
std::barrier 以其简洁的接口、自动复用计数以及低延迟的实现,成为现代 C++ 并发编程的理想选择。无论是多线程数据并行、管道式流水线,还是需要频繁同步的高性能计算,barrier 都能以最小的代码量,提供最稳健的同步机制。随着 C++20 的推广,建议在新项目中优先考虑使用 std::barrier 替代传统的锁与条件变量组合,以获得更清晰、更高效的并发代码。