C++latch一次性线程同步C++20

2026-03-22 20:45:34 245阅读

C++20 中的 std::latch:轻量级一次性线程同步机制详解

在多线程编程中,线程同步是保障数据一致性和执行时序的关键环节。C++11 引入了 std::mutexstd::condition_variable 等基础工具,C++14 和 C++17 补充了 std::shared_mutexstd::scoped_lock,而 C++20 则带来了更现代、更语义清晰的同步原语——std::latchstd::barrierstd::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::mutexstd::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++ 的体现,也是编写清晰、可靠并发程序的重要一步。

文章版权声明:除非注明,否则均为Dark零点博客原创文章,转载或复制请以超链接形式并注明出处。

目录[+]