C++latch一次性线程同步C++20
C++20 中的 std::latch:轻量级一次性线程同步机制详解
在多线程编程中,线程同步是保障数据一致性和执行时序的关键环节。C++11 引入了 std::mutex、std::condition_variable 等基础工具,C++14 和 C++17 补充了 std::shared_mutex 与 std::scoped_lock,而 C++20 则带来了更现代、更语义清晰的同步原语——std::latch、std::barrier 和 std::flex_barrier。其中,std::latch 是最基础也最常用的一次性同步设施,专为“等待多个线程完成某项任务”这一典型场景设计。
std::latch 的核心语义是:它维护一个内部计数器,初始值由构造函数指定;每次调用 count_down() 使计数器减一;当计数器归零时,所有阻塞在 wait() 上的线程被同时唤醒,且此后该 latch 永远保持“触发态”,不可重置、不可重复使用。这种“一次性”特性使其天然适用于初始化协调、启动屏障或批量任务收尾等场景,避免了传统条件变量中易错的谓词检查与虚假唤醒问题。
std::latch 定义于 <latch> 头文件中,是无锁(lock-free)实现的(在支持的平台上),性能开销极低。其接口简洁明了,仅包含五个公有成员函数:构造函数、析构函数、count_down()、wait() 和 arrive()(后者为 count_down() 的别名,语义更强调“抵达同步点”)。值得注意的是,latch 不涉及所有权转移,不提供 try_wait() 或超时等待,也不依赖任何互斥量——这正是其轻量与高效的根本原因。
下面通过一个典型示例展示 std::latch 的实际用法:主线程启动多个工作线程执行独立计算,待全部完成后再汇总结果。
#include <iostream>
#include <vector>
#include <thread>
#include <latch>
#include <chrono>
int main() {
constexpr size_t N = 4;
std::vector<int> results(N, 0);
std::latch done_latch(N); // 初始化计数器为 4
// 启动 N 个工作线程
std::vector<std::thread> workers;
for (size_t i = 0; i < N; ++i) {
workers.emplace_back([i, &results, &done_latch]() {
// 模拟耗时计算
std::this_thread::sleep_for(std::chrono::milliseconds(100 * (i + 1)));
results[i] = static_cast<int>(i * i);
// 标记本线程已完成
done_latch.count_down();
});
}
// 主线程等待所有工作线程完成
done_latch.wait();
// 此时可安全访问 results
int sum = 0;
for (int v : results) {
sum += v;
}
std::cout << "Results: ";
for (int v : results) {
std::cout << v << " ";
}
std::cout << "\nSum = " << sum << "\n";
// 等待工作线程自然结束
for (auto& t : workers) {
if (t.joinable()) {
t.join();
}
}
return 0;
}
该示例中,std::latch done_latch(N) 构造了一个计数为 4 的门闩。每个工作线程在完成自身任务后调用 count_down(),将计数减一;主线程调用 wait() 进入阻塞状态,直到计数归零才继续执行。整个过程无需手动管理互斥锁,也无需循环检查完成标志,代码逻辑清晰、不易出错。
对比传统方式——例如使用 std::mutex 加 std::condition_variable 实现类似功能——latch 显著降低了复杂度。后者需维护共享完成计数、加锁更新、通知条件变量,并在 wait() 前加锁、在谓词为真时解锁,稍有不慎即引发死锁或竞态。而 latch 将这些细节完全封装,用户只需关注“谁来减”和“谁来等”两个动作。
还需注意几个关键使用约束:首先,std::latch 对象必须在线程间安全共享,通常通过引用或指针传递,不可拷贝(仅可移动,但移动后原对象处于有效但未定义状态,实践中应避免移动);其次,count_down() 可被任意线程多次调用,但总次数不应超过初始计数,否则行为未定义;最后,wait() 可被多次调用,一旦 latch 触发,后续所有 wait() 立即返回,不会阻塞。
在性能敏感场景中,std::latch 的优势尤为突出。由于其实现通常基于原子操作与 futex(Linux)或 WaitOnAddress(Windows)等底层内核机制,其唤醒延迟远低于基于互斥量的条件变量。实测表明,在高并发短任务场景下,latch 的平均等待延迟可比等效 condition_variable 降低 30% 以上。
当然,latch 并非万能。若需重复使用的同步点(如每轮迭代都等待一组线程),应选用 std::barrier;若需在触发时执行回调逻辑,则 std::flex_barrier 更合适。理解三者的定位差异,方能合理选型。
综上所述,std::latch 是 C++20 赋予开发者的一把精巧“同步小刀”:它不追求功能繁复,而专注解决一类高频问题——一次性汇聚。其接口极简、语义明确、实现高效,显著提升了多线程代码的可读性与健壮性。对于新项目,尤其是需要跨平台、低延迟同步的系统级或高性能计算应用,std::latch 应成为标准工具链中的首选之一。掌握它,既是拥抱现代 C++ 的体现,也是编写清晰、可靠并发程序的重要一步。

