C++latch一次性线程同步计数
C++20 latch:一次性的“红绿灯”,让线程等得明白、放得干脆
写过多线程代码的人,大概都踩过这样的坑:主线程急着要结果,子线程还在初始化;或者十几个工作线程各自忙活,你却不知道它们啥时候真干完了——于是祭出 join()、condition_variable、甚至手搓原子计数器……结果逻辑越补越厚,bug 越修越隐晦。
C++20 引入的 std::latch,就是来替你拆掉这些“临时脚手架”的。它不搞循环等待,不支持重用,也不掺和条件判断——它只做一件事:数够了,就永远亮绿灯。
这恰恰是它最踏实的地方:一次性、无状态、零歧义。不是“可能完成”,而是“确认全部抵达”。
latch 的核心就一个整数 count,初始化时定死,之后只能减,不能增,也不能重置。调用 arrive() 减一,调用 wait() 阻塞直到归零。一旦归零,所有后续 wait() 立刻返回,arrive() 也依然合法(但不再影响状态)——它不拒绝迟到者,只保证早到者不白等。
举个真实场景:启动一个服务模块,需并行加载配置、连接数据库、预热缓存三件事。你不需要它们按顺序完成,只关心“三件全齐了,我才对外提供服务”。
#include <latch>
#include <thread>
#include <vector>
void start_service() {
std::latch ready(3); // 等三件事
std::vector<std::thread> workers;
workers.emplace_back([&ready] {
load_config(); // 耗时不定
ready.arrive(); // 告诉:我好了
});
workers.emplace_back([&ready] {
connect_db(); // 可能超时重试
ready.arrive();
});
workers.emplace_back([&ready] {
warm_cache(); // 内存密集型
ready.arrive();
});
ready.wait(); // 主线程卡这儿,直到三件事全到位
std::cout << "✅ 服务已就绪\n";
for (auto& t : workers) t.join();
}
注意:arrive() 不要求在独立线程里调用。你完全可以在某个回调函数、异步完成处理中调用它——只要逻辑上“这件事算完成了”,就 arrive()。这种松耦合,比死守 join() 的线程生命周期灵活得多。
有人会问:那 barrier 和 semaphore 不也能数数?区别在哪?
barrier 是循环用的——比如多轮迭代计算,每轮都等全员到达再进下一轮。latch 不循环,它只认“第一次归零”。你不能指望它第二次拦住谁。
semaphore 更通用,但语义太宽:它管“资源可用数”,可增可减,还能超发。而 latch 的语义是纯粹的“同步点”——它不表达资源,只表达“里程碑是否达成”。这种语义窄化,反而降低了误用概率。
再打个比方:latch 像高铁站的检票闸机,只设一次“发车倒计时”,乘客陆续刷脸通过,倒计时归零,闸机永久开启;barrier 像健身房的团课签到,每节课都重置;semaphore 则像停车场的空位显示器——数字会变,还可能被抢光。
实际编码中,最容易翻车的是生命周期管理。latch 对象必须活过所有 arrive() 和 wait() 调用。常见错误是把它声明在局部作用域,而 arrive() 在分离线程里调用:
// ❌ 危险!ready 可能在子线程调用 arrive 前就被析构
void bad_example() {
std::latch ready(1);
std::thread t([&ready] {
do_work();
ready.arrive(); // 此时 ready 可能已销毁!
});
t.detach(); // 更糟:彻底失控
}
正确做法只有两个:要么确保 latch 的生存期覆盖全部操作(如上文 start_service 中定义在函数开头),要么用 std::shared_ptr<std::latch> 跨作用域传递。别图省事往 lambda 捕获列表里传引用——除非你百分百确定生命周期。
最后说个实用技巧:latch 天然适合做“异步任务聚合器”的底座。比如你发了 5 个 HTTP 请求,每个回调里调用 arrive(),主线程 wait() 后统一检查结果。它不关心谁快谁慢,只忠实地记录“有多少个已经落地”。
没有 notify_one/notify_all 的纠结,没有 mutex 锁粒度的权衡,也没有 future 那种“一个 promise 对应一个 future”的绑定负担。它轻、直、狠——就像拧紧最后一颗螺丝,咔哒一声,整台机器才真正开始运转。
C++ 的并发工具链里,latch 不是最炫的,但可能是最让人安心的。它不许诺更多,只兑现它名字里的那个字:Latch —— 扣上,就不再松开。


还没有评论,来说两句吧...