C++ STL 之 latch / barrier / semaphore 详解(C++20 并发新原语)
一、为什么需要新的并发原语
C++11 提供了 std::thread、std::mutex、std::condition_variable 和 std::future,但这些工具在面对"等待一组任务完成"、"限制并发数量"等常见场景时,要么需要手写复杂的同步逻辑,要么性能不够理想。C++20 从 POSIX 和 Java 等生态引入了三个标准库同步原语:
| 原语 | 作用 | 是否可复用 |
|---|---|---|
std::latch |
一次性同步点,N 个线程到达后放行 | 否 |
std::barrier |
可复用同步点,每轮到达触发回调 | 是 |
std::counting_semaphore |
计数信号量,控制资源访问数 | 是 |
它们都定义在 <latch>、<barrier>、<semaphore> 头文件中,底层通常基于原子操作或 futex 实现,比条件变量方案更轻量。
二、latch------一次性栅栏
std::latch 是一个向下计数的一次性屏障 。构造时指定初始计数,线程调用 count_down() 减少计数,调用 wait() 阻塞直到计数归零。计数到达零后永久保持,不可重置。
cpp
#include <latch>
#include <thread>
#include <vector>
#include <iostream>
void demo_latch() {
std::latch done{3}; // 等待 3 个任务
std::vector<std::jthread> workers;
for (int i = 0; i < 3; ++i) {
workers.emplace_back([&, i] {
// 模拟工作
std::this_thread::sleep_for(std::chrono::milliseconds(100 * i));
std::cout << "worker " << i << " done\n";
done.count_down(); // 计数减 1
});
}
done.wait(); // 等待所有 3 个 worker
std::cout << "all workers done, proceed\n";
}
关键 API:
count_down(n = 1)------ 计数减 n,不阻塞wait()------ 阻塞直到计数为 0arrive_and_wait(n = 1)------ 等价于count_down(n)+wait()try_wait()------ 非阻塞检查计数是否为 0
典型场景:初始化加载、多路并行计算后合并结果、所有线程就绪后同时开始。
三、barrier------可复用栅栏
std::barrier 是 latch 的升级版。它像一个"回合制"同步点:每轮 N 个线程全部到达后,一起放行,然后自动进入下一轮。每轮结束时还会执行一个完成回调(completion function)。
cpp
#include <barrier>
#include <thread>
#include <iostream>
void demo_barrier() {
constexpr int N = 3;
int round = 0;
std::barrier sync{N, [&]() noexcept {
std::cout << "=== round " << ++round << " done ===\n";
}};
std::vector<std::jthread> workers;
for (int i = 0; i < N; ++i) {
workers.emplace_back([&, i] {
for (int r = 0; r < 3; ++r) {
std::this_thread::sleep_for(std::chrono::milliseconds(50 * i));
std::cout << "worker " << i << " arrives at round " << r << "\n";
sync.arrive_and_wait(); // 到达并等待本轮所有人
}
});
}
}
每次 arrive_and_wait() 返回时,所有线程都位于同一轮次的同一时间点------这就是 barrier 的核心保证。
#mermaid-svg-TzvfkCMQ8KQGJiYx{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-TzvfkCMQ8KQGJiYx .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-TzvfkCMQ8KQGJiYx .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-TzvfkCMQ8KQGJiYx .error-icon{fill:#552222;}#mermaid-svg-TzvfkCMQ8KQGJiYx .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-TzvfkCMQ8KQGJiYx .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-TzvfkCMQ8KQGJiYx .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-TzvfkCMQ8KQGJiYx .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-TzvfkCMQ8KQGJiYx .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-TzvfkCMQ8KQGJiYx .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-TzvfkCMQ8KQGJiYx .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-TzvfkCMQ8KQGJiYx .marker{fill:#333333;stroke:#333333;}#mermaid-svg-TzvfkCMQ8KQGJiYx .marker.cross{stroke:#333333;}#mermaid-svg-TzvfkCMQ8KQGJiYx svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-TzvfkCMQ8KQGJiYx p{margin:0;}#mermaid-svg-TzvfkCMQ8KQGJiYx .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-TzvfkCMQ8KQGJiYx .cluster-label text{fill:#333;}#mermaid-svg-TzvfkCMQ8KQGJiYx .cluster-label span{color:#333;}#mermaid-svg-TzvfkCMQ8KQGJiYx .cluster-label span p{background-color:transparent;}#mermaid-svg-TzvfkCMQ8KQGJiYx .label text,#mermaid-svg-TzvfkCMQ8KQGJiYx span{fill:#333;color:#333;}#mermaid-svg-TzvfkCMQ8KQGJiYx .node rect,#mermaid-svg-TzvfkCMQ8KQGJiYx .node circle,#mermaid-svg-TzvfkCMQ8KQGJiYx .node ellipse,#mermaid-svg-TzvfkCMQ8KQGJiYx .node polygon,#mermaid-svg-TzvfkCMQ8KQGJiYx .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-TzvfkCMQ8KQGJiYx .rough-node .label text,#mermaid-svg-TzvfkCMQ8KQGJiYx .node .label text,#mermaid-svg-TzvfkCMQ8KQGJiYx .image-shape .label,#mermaid-svg-TzvfkCMQ8KQGJiYx .icon-shape .label{text-anchor:middle;}#mermaid-svg-TzvfkCMQ8KQGJiYx .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-TzvfkCMQ8KQGJiYx .rough-node .label,#mermaid-svg-TzvfkCMQ8KQGJiYx .node .label,#mermaid-svg-TzvfkCMQ8KQGJiYx .image-shape .label,#mermaid-svg-TzvfkCMQ8KQGJiYx .icon-shape .label{text-align:center;}#mermaid-svg-TzvfkCMQ8KQGJiYx .node.clickable{cursor:pointer;}#mermaid-svg-TzvfkCMQ8KQGJiYx .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-TzvfkCMQ8KQGJiYx .arrowheadPath{fill:#333333;}#mermaid-svg-TzvfkCMQ8KQGJiYx .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-TzvfkCMQ8KQGJiYx .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-TzvfkCMQ8KQGJiYx .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-TzvfkCMQ8KQGJiYx .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-TzvfkCMQ8KQGJiYx .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-TzvfkCMQ8KQGJiYx .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-TzvfkCMQ8KQGJiYx .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-TzvfkCMQ8KQGJiYx .cluster text{fill:#333;}#mermaid-svg-TzvfkCMQ8KQGJiYx .cluster span{color:#333;}#mermaid-svg-TzvfkCMQ8KQGJiYx div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-TzvfkCMQ8KQGJiYx .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-TzvfkCMQ8KQGJiYx rect.text{fill:none;stroke-width:0;}#mermaid-svg-TzvfkCMQ8KQGJiYx .icon-shape,#mermaid-svg-TzvfkCMQ8KQGJiYx .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-TzvfkCMQ8KQGJiYx .icon-shape p,#mermaid-svg-TzvfkCMQ8KQGJiYx .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-TzvfkCMQ8KQGJiYx .icon-shape .label rect,#mermaid-svg-TzvfkCMQ8KQGJiYx .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-TzvfkCMQ8KQGJiYx .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-TzvfkCMQ8KQGJiYx .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-TzvfkCMQ8KQGJiYx :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 线程 1 到达
barrier 等待
线程 2 到达
线程 3 到达
执行完成回调
所有线程放行
线程 1 下一轮
线程 2 下一轮
线程 3 下一轮
关键 API 与 latch 的差异:
arrive(n = 1)------ 到达,返回std::barrier::arrival_token(用于wait()分离)arrive_and_wait()------ 到达并等待arrive_and_drop()------ 永久减少本轮参与线程数(退出专用)
典型场景:迭代计算中每轮结束后同步、游戏帧循环中的多线程阶段同步、流水线作业中所有阶段对齐。
四、semaphore------计数信号量
std::counting_semaphore<LeastMaxValue> 是一个计数信号量 ,模板参数 LeastMaxValue 指定最大计数的下限。std::binary_semaphore 是其特化 counting_semaphore<1>。
cpp
#include <semaphore>
#include <thread>
#include <queue>
#include <iostream>
std::counting_semaphore<10> sem{3}; // 最多 3 个并发
std::mutex mtx;
std::queue<int> q;
void producer() {
for (int i = 0; i < 10; ++i) {
sem.acquire(); // P 操作:计数减 1,满则阻塞
{
std::lock_guard lk{mtx};
q.push(i);
std::cout << "produce " << i << "\n";
}
}
}
void consumer() {
for (int i = 0; i < 10; ++i) {
int val;
{
std::lock_guard lk{mtx};
if (!q.empty()) {
val = q.front();
q.pop();
}
}
sem.release(); // V 操作:计数加 1,唤醒等待者
std::cout << "consume " << val << "\n";
}
}
#mermaid-svg-ZsQdfYWaMzfKvNeA{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ZsQdfYWaMzfKvNeA .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ZsQdfYWaMzfKvNeA .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ZsQdfYWaMzfKvNeA .error-icon{fill:#552222;}#mermaid-svg-ZsQdfYWaMzfKvNeA .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ZsQdfYWaMzfKvNeA .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ZsQdfYWaMzfKvNeA .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ZsQdfYWaMzfKvNeA .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ZsQdfYWaMzfKvNeA .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ZsQdfYWaMzfKvNeA .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ZsQdfYWaMzfKvNeA .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ZsQdfYWaMzfKvNeA .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ZsQdfYWaMzfKvNeA .marker.cross{stroke:#333333;}#mermaid-svg-ZsQdfYWaMzfKvNeA svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ZsQdfYWaMzfKvNeA p{margin:0;}#mermaid-svg-ZsQdfYWaMzfKvNeA .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ZsQdfYWaMzfKvNeA .cluster-label text{fill:#333;}#mermaid-svg-ZsQdfYWaMzfKvNeA .cluster-label span{color:#333;}#mermaid-svg-ZsQdfYWaMzfKvNeA .cluster-label span p{background-color:transparent;}#mermaid-svg-ZsQdfYWaMzfKvNeA .label text,#mermaid-svg-ZsQdfYWaMzfKvNeA span{fill:#333;color:#333;}#mermaid-svg-ZsQdfYWaMzfKvNeA .node rect,#mermaid-svg-ZsQdfYWaMzfKvNeA .node circle,#mermaid-svg-ZsQdfYWaMzfKvNeA .node ellipse,#mermaid-svg-ZsQdfYWaMzfKvNeA .node polygon,#mermaid-svg-ZsQdfYWaMzfKvNeA .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ZsQdfYWaMzfKvNeA .rough-node .label text,#mermaid-svg-ZsQdfYWaMzfKvNeA .node .label text,#mermaid-svg-ZsQdfYWaMzfKvNeA .image-shape .label,#mermaid-svg-ZsQdfYWaMzfKvNeA .icon-shape .label{text-anchor:middle;}#mermaid-svg-ZsQdfYWaMzfKvNeA .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ZsQdfYWaMzfKvNeA .rough-node .label,#mermaid-svg-ZsQdfYWaMzfKvNeA .node .label,#mermaid-svg-ZsQdfYWaMzfKvNeA .image-shape .label,#mermaid-svg-ZsQdfYWaMzfKvNeA .icon-shape .label{text-align:center;}#mermaid-svg-ZsQdfYWaMzfKvNeA .node.clickable{cursor:pointer;}#mermaid-svg-ZsQdfYWaMzfKvNeA .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ZsQdfYWaMzfKvNeA .arrowheadPath{fill:#333333;}#mermaid-svg-ZsQdfYWaMzfKvNeA .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ZsQdfYWaMzfKvNeA .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ZsQdfYWaMzfKvNeA .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ZsQdfYWaMzfKvNeA .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ZsQdfYWaMzfKvNeA .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ZsQdfYWaMzfKvNeA .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ZsQdfYWaMzfKvNeA .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ZsQdfYWaMzfKvNeA .cluster text{fill:#333;}#mermaid-svg-ZsQdfYWaMzfKvNeA .cluster span{color:#333;}#mermaid-svg-ZsQdfYWaMzfKvNeA div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-ZsQdfYWaMzfKvNeA .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ZsQdfYWaMzfKvNeA rect.text{fill:none;stroke-width:0;}#mermaid-svg-ZsQdfYWaMzfKvNeA .icon-shape,#mermaid-svg-ZsQdfYWaMzfKvNeA .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ZsQdfYWaMzfKvNeA .icon-shape p,#mermaid-svg-ZsQdfYWaMzfKvNeA .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ZsQdfYWaMzfKvNeA .icon-shape .label rect,#mermaid-svg-ZsQdfYWaMzfKvNeA .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ZsQdfYWaMzfKvNeA .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ZsQdfYWaMzfKvNeA .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ZsQdfYWaMzfKvNeA :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
acquire()
计数 > 0 ?
计数减 1
继续执行
阻塞等待
有 release() 调用
release()
计数加 1
唤醒等待者
关键 API:
acquire()------ P 操作,计数减 1,不足时阻塞release(n = 1)------ V 操作,计数加 n,唤醒等待者try_acquire()------ 非阻塞版 acquire,失败返回 falsetry_acquire_for(dur)------ 带超时的 acquire
典型场景:线程池任务队列限流、连接池资源分配、生产者-消费者协调。
五、与 C++11 条件变量方案对比
5.1 latch vs. condition_variable
用条件变量实现"等待 N 个线程完成":
cpp
// C++11 方式
int count = 3;
std::mutex mtx;
std::condition_variable cv;
{
std::unique_lock lk{mtx};
if (--count == 0)
cv.notify_all();
else
cv.wait(lk, [&]{ return count == 0; });
}
问题:
- 需要额外
mutex和int计数器 - 虚假唤醒(spurious wakeup)必须用谓词循环检查
notify_all()唤醒所有等待者,即使计数没到零
用 std::latch:
cpp
std::latch done{3};
done.count_down(); // 任何线程
done.wait(); // 等待线程
- 零额外变量,零虚假唤醒
- 底层通常基于
std::atomic<int>+ futex,无锁路径
5.2 barrier vs. 手写循环
手写可复用栅栏需要条件变量 + 计数器 + 轮次标志,逻辑极易出错。std::barrier 封装了全部状态机,且完成回调的执行保证:恰好有一个线程执行回调,其他线程在回调完成后才被放行。
5.3 semaphore vs. condition_variable
| 维度 | condition_variable |
counting_semaphore |
|---|---|---|
| 状态 | 无状态,需外部变量 | 自带计数状态 |
| 虚假唤醒 | 可能发生,需谓词检查 | 不产生 |
| 多资源管理 | 需自行计算 | acquire/release 原生支持 |
| 超时 | 支持 wait_for/until | 同上 |
六、面试常考题
Q1:latch 和 barrier 的根本区别是什么?
latch 一次性使用,计数归零后永久放行;barrier 每轮结束后自动重置,支持多轮同步且可设完成回调。
Q2:barrier 的 completion function 由哪个线程执行?
由每轮最后一个调用 arrive_and_wait() 的线程执行,且执行期间其他线程仍处于阻塞状态。
Q3:binary_semaphore 可以替代 mutex 吗?
不能直接替代。binary_semaphore 的 release() 可以在任意线程调用,而 mutex 要求同一线程 unlock。但正因如此,binary_semaphore 适合实现信号量传递(如生产者通知消费者)。
Q4:counting_semaphore<5> 和 counting_semaphore<10> 在运行时有区别吗?
没有。模板参数只是最大计数的下限声明,运行时行为取决于初始值和 release 调用。不同特化类型的对象也不能相互赋值。
Q5:C++20 的并发原语能替代 std::future 吗?
不能完全替代。latch/barrier 关注线程同步点,future 关注异步结果传递。它们解决的问题正交,常配合使用。
Q6:什么场景下 barrier 比 latch 更适合?
任何需要多轮同步的场景:比如迭代求解(每轮结束后广播中间结果)、帧动画渲染(每帧多线程分工后合并、下一帧继续)。latch 只适合一次性的"等待初始化完成"或"等待所有任务提交"。
七、总结
latch/barrier/semaphore 填补了 C++11 标准库在同步原语上的空白。它们比手写条件变量方案更简洁、性能更好(无虚假唤醒、原子操作更快),并且语义明确、不易出错。在新项目中,凡是遇到"等待 N 个线程"或"限制并发数量"的需求,优先考虑这三个标准原语。