std::latch初始化值必须等于count_down()总调用次数;设为0则wait立即返回,设小会导致ub,设大则永久阻塞;它不可重用、不支持超时和中断,且需严格保证跨线程生命周期安全。

std::latch 初始化值必须等于等待线程数
初始化 std::latch 时传的整数,是它内部计数器的初始值,不是“要等几个线程”,而是“要 count_down() 多少次才变零”。如果开 5 个线程,每个都调一次 count_down(),就得初始化为 5。设成 1 或 0,要么提前唤醒,要么永远卡死。
- 设成
0:构造完就已就绪,wait()立刻返回 —— 这不是 bug,但容易误以为“没生效” - 设成
3但有 5 个线程调count_down():第 4 次调用会 UB(未定义行为),Clang/GCC 在 debug 模式下可能 abort - 典型场景:主线程启动 N 个 worker 线程,每个 worker 完成后调一次
count_down(),主线程wait()等全部结束
std::latch 不可重用,用完即弃
std::latch 是一次性同步原语。一旦计数器归零,wait() 总是立即返回,count_down() 不再改变状态,也不能 reset 或重新设置计数。想重复同步,得 new 一个新对象,或者换用 std::barrier。
- 错误写法:
latch.wait(); latch.count_down(); latch.wait();—— 第二个wait()不阻塞 - 替代方案:
std::barrier支持arrive_and_wait()循环使用,但需显式指定参与方数量且不能动态增减 - 性能提示:
std::latch通常比std::condition_variable轻量,无锁实现常见,适合一等多、不重入的场景
std::latch 在线程间传递需注意生命周期
把 std::latch 对象传给子线程时,必须确保它在所有线程调用 count_down() 或 wait() 期间始终有效。局部变量直接传引用?大概率 crash。
- 安全做法:堆上分配(
std::shared_ptr<:latch></:latch>),或作为类成员/全局对象管理生命周期 - 危险写法:
std::thread{[&latch] { latch.count_down(); }};,其中latch是栈变量且主线程很快退出作用域 - 错误现象:子线程访问已析构的
latch,触发 SIGSEGV 或静默内存破坏,调试困难 - C++20 要求
std::latch是 trivially destructible,但它内部可能持有原子变量或系统句柄,析构非空操作
std::latch wait() 不响应中断,也不支持超时
std::latch::wait() 是纯阻塞调用,没有 wait_until() 或 wait_for() 变体,也没办法被信号、取消请求或异常中断。一旦卡住,只能靠外部机制(比如共享 flag + try_wait() 轮询)来保底。
- 若需超时:手动轮询
try_wait()配合std::this_thread::sleep_for(),但失去原子性优势 -
try_wait()是无副作用的只读检查,返回bool表示当前是否已就绪 - 对比
std::condition_variable:虽更重,但支持精确超时和 notify_one/notify_all 粒度控制 - 实际建议:仅用于“确定能完成”的短时协作;长期任务或需容错的场景,别硬扛
wait()
最易忽略的是生命周期和初始化值的耦合关系:值设错不会编译报错,运行时表现却完全失控,而且问题在线程交织下难以复现。写完先盯两眼构造参数和谁在哪儿调 count_down()。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!









