当前位置: 首页 > java >正文

【C++】 一文读懂 std::latch

std::latch 是 C++20 引入的新特性,本文将带你从基础到高级逐步了解它。只需 5 分钟,你就能轻松掌握 std::latch 的核心用法。

一、基础概念与核心功能

1. 什么是std::latch

std::latch是C++20引入的单次使用的同步原语,用于协调多个线程的执行。它通过一个向下计数器实现同步,当计数器减至零时,所有等待的线程被唤醒。

核心特性:

  • 单次使用:计数器归零后无法重置或复用。
  • 线程安全:成员函数(除析构函数)可被多线程并发调用。
  • 非阻塞操作:count_down()立即返回,wait()阻塞直到计数器归零。

2. 核心成员函数

函数名行为描述
count_down(n=1)非阻塞地减少计数器(默认减1),若计数器归零则唤醒所有等待线程
try_wait()非阻塞检查计数器是否为零,返回布尔值
wait()阻塞当前线程,直到计数器归零
arrive_and_wait()组合操作:count_down()后立即调用wait()

3. 基本使用示例

#include <latch>
#include <thread>std::latch sync_point(3);  // 初始计数器为3void worker() {// ... 执行任务sync_point.count_down();  // 任务完成,计数器减1
}int main() {std::thread t1(worker), t2(worker), t3(worker);sync_point.wait();  // 主线程等待所有任务完成t1.join(); t2.join(); t3.join();
}

此例中,主线程通过wait()阻塞,直到3个工作线程调用count_down()使计数器归零。


二、进阶特性与实现原理

1. 与std::atomic的对比

传统使用原子变量实现类似功能:

std::atomic<int> counter(3);
// 线程中:counter.fetch_sub(1);
while (counter > 0);  // 忙等待(低效)

std::latch的优势:

  • 避免忙等待:通过条件变量实现阻塞,减少CPU资源浪费。
  • 代码简洁性:无需手动管理同步逻辑。

2. 内部实现机制

std::latch通常通过原子计数器+条件变量+互斥锁实现:

class latch {std::atomic<int> count_;std::mutex mtx_;std::condition_variable cv_;
public:explicit latch(int count) : count_(count) {}void count_down() {std::lock_guard lock(mtx_);if (--count_ == 0) cv_.notify_all();}void wait() {std::unique_lock lock(mtx_);cv_.wait(lock, [this]{ return count_ == 0; });}
};

源码贴这儿,对照着看:

_STD_BEGIN_EXPORT_STD class latch {
public:_NODISCARD static constexpr ptrdiff_t(max)() noexcept {return PTRDIFF_MAX;}constexpr explicit latch(const ptrdiff_t _Expected) noexcept /* strengthened */ : _Counter{_Expected} {_STL_VERIFY(_Expected >= 0, "Precondition: expected >= 0 (N4950 [thread.latch.class]/4)");}latch(const latch&)            = delete;latch& operator=(const latch&) = delete;void count_down(const ptrdiff_t _Update = 1) noexcept /* strengthened */ {_STL_VERIFY(_Update >= 0, "Precondition: update >= 0 (N4950 [thread.latch.class]/7)");// TRANSITION, GH-1133: should be memory_order_releaseconst ptrdiff_t _Current = _Counter.fetch_sub(_Update) - _Update;if (_Current == 0) {_Counter.notify_all();} else {_STL_VERIFY(_Current >= 0, "Precondition: update <= counter (N4950 [thread.latch.class]/7)");}}_NODISCARD_TRY_WAIT bool try_wait() const noexcept {// TRANSITION, GH-1133: should be memory_order_acquirereturn _Counter.load() == 0;}void wait() const noexcept /* strengthened */ {for (;;) {// TRANSITION, GH-1133: should be memory_order_acquireconst ptrdiff_t _Current = _Counter.load();if (_Current == 0) {return;} else {_STL_VERIFY(_Current > 0, "Invariant counter >= 0, possibly caused by preconditions violation ""(N4950 [thread.latch.class]/7)");}_Counter.wait(_Current, memory_order_relaxed);}}void arrive_and_wait(const ptrdiff_t _Update = 1) noexcept /* strengthened */ {_STL_VERIFY(_Update >= 0, "Precondition: update >= 0 (N4950 [thread.latch.class]/7)");// TRANSITION, GH-1133: should be memory_order_acq_relconst ptrdiff_t _Current = _Counter.fetch_sub(_Update) - _Update;if (_Current == 0) {_Counter.notify_all();} else {_STL_VERIFY(_Current > 0, "Precondition: update <= counter (N4950 [thread.latch.class]/7)");_Counter.wait(_Current, memory_order_relaxed);wait();}}private:atomic<ptrdiff_t> _Counter;
};_STD_END

三、高级用法与场景分析

1. 组合操作arrive_and_wait()

适用于需要同时减少计数并等待的场景:

void worker(std::latch& latch) {// ... 第一阶段任务latch.arrive_and_wait();  // 减1后等待其他线程// ... 第二阶段任务(所有线程同步后继续)
}

此操作简化代码,避免手动分开调用count_down()wait()

2. 资源初始化同步

确保所有资源初始化完成后主线程继续:

#include <latch>
#include <thread>
#include <vector>
#include <iostream>
using namespace std;std::latch sync_point(3);  // 初始计数器为3struct Resource {void load_data() {std::cout << __FUNCTION__ << " loading data..."  << std::this_thread::get_id() << std::endl;}
};std::latch init_latch(5);
std::vector<Resource> resources(5);void init_resource(int id) {resources[id].load_data();  // 初始化资源init_latch.count_down();
}int main() {for (int i = 0; i < 5; ++i)std::thread(init_resource, i).detach();init_latch.wait();  // 等待所有资源初始化完成// 使用资源...
}

3. 与std::barrier的对比

特性std::latchstd::barrier
复用性单次使用可重复使用
阶段控制支持多阶段同步
适用场景一次性任务(如初始化)分阶段并行算法(如迭代计算)

四、常见问题与调试技巧

1. 计数器未归零导致死锁

问题:若count_down()调用次数不足,wait()将永久阻塞。

解决:确保每个线程严格调用一次count_down()

2. 错误复用

问题:尝试复用已归零的latch会导致未定义行为。

解决:需重新创建新对象,或改用std::barrier

3. 生命周期管理

陷阱:若latch对象在wait()调用前被销毁,导致悬垂引用。

解决:确保latch的生命周期覆盖所有线程操作。

五、实际应用案例

1. 并行任务分阶段执行

void phased_processing() {std::latch phase1(4), phase2(4);auto worker = [&] {// 阶段1phase1.arrive_and_wait();// 阶段2phase2.arrive_and_wait();};std::jthread t1(worker), t2(worker), t3(worker), t4(worker);
}

此模式需配合多个latch对象实现多阶段同步(更推荐使用std::barrier

2. 动态线程池任务协调

class Task
{
public:void excute(){}
};void process_batch(std::vector<Task>& tasks) {std::latch completion_latch(tasks.size());for (auto& task : tasks) {thread_pool.submit([&] {task.execute();completion_latch.count_down();});}completion_latch.wait();  // 等待所有任务完成
}

六、总结

std::latch作为C++20引入的线程同步工具,为一次性多线程协作提供了简洁高效的解决方案。它通过封装同步逻辑,不仅简化了代码实现,还显著提升了可读性。与std::latch不同,std::barrier更适合需要重复同步的场景。在实际应用中,开发者应特别关注其生命周期管理,并确保计数器调用的准确性,以规避潜在问题。

http://www.xdnf.cn/news/6701.html

相关文章:

  • XML介绍及常用c及c++库
  • Python 在Excel单元格中应用多种字体样式
  • 5月16日复盘-目标检测开端
  • Grafana分布统计:Heatmap面板
  • 关于 2025 年国产化 AI 算力盒子的报告
  • 一发入魂:极简解决 SwiftUI 复杂视图未能正确刷新的问题(中)
  • 响应式布局
  • bili.png
  • Vulfocus靶场-文件上传-3
  • threejs小案例——贴图翻转
  • Android App CAN通信测试
  • opencloudos 安装 mosquitto
  • 操作系统|| 虚拟内存页置换算法
  • 遥感图像露天矿区检测数据集VOC+YOLO格式1542张1类别
  • (for 循环) VS (LINQ) 性能比拼 ——c#
  • 【HTML5学习笔记1】html标签(上)
  • javascript和vue的不同
  • 机器学习数据预处理回归预测中标准化和归一化
  • React Flow 节点属性详解:类型、样式与自定义技巧
  • 从技术视角解构 Solana Meme 币生态
  • 校园一卡通安全策略研究调研报告
  • 【配置中心】配置中心该用Nacos还是Apollo
  • 【C++】类与对象
  • python 爬虫框架介绍
  • Day11-苍穹外卖(数据统计篇)
  • 机器学习-特征工程
  • LED点阵屏模块
  • uniapp+vue3页面滚动加载数据
  • 交叉熵损失函数,KL散度, Focal loss
  • 经典启发算法【早期/启发式/HC爬山/SA模拟退火/TS禁忌搜/IA免疫 思想流程举例全】