C++ STL 之 latch / barrier / semaphore 详解(C++20 并发新原语)

现代C++并发编程实战:从多线程到线程池的完整指南 并发编程是现代软件开发的核心技术,它通过允许多个任务同时执行来提升程序性能和资源利用率。其基本原理涉及任务调度与同步机制,旨在解决多核处理器环境下的性能瓶颈问题。在技术价值层面,并发编程能够显著提升高并发网络服务器、实时图形界面及大数据处理等应用的吞吐量和响应速度。C++作为高性能系统开发的主要语言,自C++11起提供了标准化的并发支持,包括线程、互斥锁、原子操作等关键组件。本文聚焦于C++并发编程的实战应用,深入解析数据竞争、死锁等常见问题的解决方案,并演示如何构建高效的线程池来处理并发任务。通过理解st 阅读详情

C++ STL 之 latch / barrier / semaphore 详解(C++20 并发新原语)

一、为什么需要新的并发原语

C++11 提供了 std::thread、std::mutex、std::condition_variable 和 std::future,但这些工具在面对"等待一组任务完成"、"限制并发数量"等常见场景时,要么需要手写复杂的同步逻辑,要么性能不够理想。C++20 从 POSIX 和 Java 等生态引入了三个标准库同步原语:

原语作用是否可复用
std::latch一次性同步点,N 个线程到达后放行否
std::barrier可复用同步点,每轮到达触发回调是
std::counting_semaphore计数信号量,控制资源访问数是

它们都定义在 <latch>、<barrier>、<semaphore> 头文件中,底层通常基于原子操作或 futex 实现,比条件变量方案更轻量。

二、latch——一次性栅栏

std::latch 是一个向下计数的一次性屏障。构造时指定初始计数,线程调用 count_down() 减少计数,调用 wait() 阻塞直到计数归零。计数到达零后永久保持,不可重置。

#include <latch>
#include <thread>
#include <vector>
#include <iostream>

void demo_latch() {
    std::latch done{3};  // 等待 3 个任务

    std::vector<std::jthread> workers;
    for (int i = 0; i < 3; ++i) {
        workers.emplace_back([&, i] {
            // 模拟工作
            std::this_thread::sleep_for(std::chrono::milliseconds(100 * i));
            std::cout << "worker " << i << " done\n";
            done.count_down();  // 计数减 1
        });
    }

    done.wait();  // 等待所有 3 个 worker
    std::cout << "all workers done, proceed\n";
}

关键 API:

  • count_down(n = 1) —— 计数减 n,不阻塞
  • wait() —— 阻塞直到计数为 0
  • arrive_and_wait(n = 1) —— 等价于 count_down(n) + wait()
  • try_wait() —— 非阻塞检查计数是否为 0

典型场景:初始化加载、多路并行计算后合并结果、所有线程就绪后同时开始。

三、barrier——可复用栅栏

std::barrier 是 latch 的升级版。它像一个"回合制"同步点:每轮 N 个线程全部到达后,一起放行,然后自动进入下一轮。每轮结束时还会执行一个完成回调(completion function)。

#include <barrier>
#include <thread>
#include <iostream>

void demo_barrier() {
    constexpr int N = 3;
    int round = 0;
    std::barrier sync{N, [&]() noexcept {
        std::cout << "=== round " << ++round << " done ===\n";
    }};

    std::vector<std::jthread> workers;
    for (int i = 0; i < N; ++i) {
        workers.emplace_back([&, i] {
            for (int r = 0; r < 3; ++r) {
                std::this_thread::sleep_for(std::chrono::milliseconds(50 * i));
                std::cout << "worker " << i << " arrives at round " << r << "\n";
                sync.arrive_and_wait();  // 到达并等待本轮所有人
            }
        });
    }
}

每次 arrive_and_wait() 返回时,所有线程都位于同一轮次的同一时间点——这就是 barrier 的核心保证。

线程 1 到达

barrier 等待

线程 2 到达

线程 3 到达

执行完成回调

所有线程放行

线程 1 下一轮

线程 2 下一轮

线程 3 下一轮

关键 API 与 latch 的差异:

  • arrive(n = 1) —— 到达,返回 std::barrier::arrival_token(用于 wait() 分离)
  • arrive_and_wait() —— 到达并等待
  • arrive_and_drop() —— 永久减少本轮参与线程数(退出专用)

典型场景:迭代计算中每轮结束后同步、游戏帧循环中的多线程阶段同步、流水线作业中所有阶段对齐。

四、semaphore——计数信号量

std::counting_semaphore<LeastMaxValue> 是一个计数信号量,模板参数 LeastMaxValue 指定最大计数的下限。std::binary_semaphore 是其特化 counting_semaphore<1>。

#include <semaphore>
#include <thread>
#include <queue>
#include <iostream>

std::counting_semaphore<10> sem{3};   // 最多 3 个并发
std::mutex mtx;
std::queue<int> q;

void producer() {
    for (int i = 0; i < 10; ++i) {
        sem.acquire();                 // P 操作:计数减 1,满则阻塞
        {
            std::lock_guard lk{mtx};
            q.push(i);
            std::cout << "produce " << i << "\n";
        }
    }
}

void consumer() {
    for (int i = 0; i < 10; ++i) {
        int val;
        {
            std::lock_guard lk{mtx};
            if (!q.empty()) {
                val = q.front();
                q.pop();
            }
        }
        sem.release();                 // V 操作:计数加 1,唤醒等待者
        std::cout << "consume " << val << "\n";
    }
}

是

否

acquire()

计数 > 0 ?

计数减 1
继续执行

阻塞等待

有 release() 调用

release()

计数加 1
唤醒等待者

关键 API:

  • acquire() —— P 操作,计数减 1,不足时阻塞
  • release(n = 1) —— V 操作,计数加 n,唤醒等待者
  • try_acquire() —— 非阻塞版 acquire,失败返回 false
  • try_acquire_for(dur) —— 带超时的 acquire

典型场景:线程池任务队列限流、连接池资源分配、生产者-消费者协调。

五、与 C++11 条件变量方案对比

5.1 latch vs. condition_variable

用条件变量实现"等待 N 个线程完成":

// C++11 方式
int count = 3;
std::mutex mtx;
std::condition_variable cv;
{
    std::unique_lock lk{mtx};
    if (--count == 0)
        cv.notify_all();
    else
        cv.wait(lk, [&]{ return count == 0; });
}

问题:

  • 需要额外 mutex 和 int 计数器
  • 虚假唤醒(spurious wakeup)必须用谓词循环检查
  • notify_all() 唤醒所有等待者,即使计数没到零

用 std::latch:

std::latch done{3};
done.count_down();     // 任何线程
done.wait();           // 等待线程
  • 零额外变量,零虚假唤醒
  • 底层通常基于 std::atomic<int> + futex,无锁路径

5.2 barrier vs. 手写循环

手写可复用栅栏需要条件变量 + 计数器 + 轮次标志,逻辑极易出错。std::barrier 封装了全部状态机,且完成回调的执行保证:恰好有一个线程执行回调,其他线程在回调完成后才被放行。

5.3 semaphore vs. condition_variable

维度condition_variablecounting_semaphore
状态无状态,需外部变量自带计数状态
虚假唤醒可能发生,需谓词检查不产生
多资源管理需自行计算acquire/release 原生支持
超时支持 wait_for/until同上

六、面试常考题

Q1:latch 和 barrier 的根本区别是什么?

latch 一次性使用,计数归零后永久放行;barrier 每轮结束后自动重置,支持多轮同步且可设完成回调。

Q2:barrier 的 completion function 由哪个线程执行?

由每轮最后一个调用 arrive_and_wait() 的线程执行,且执行期间其他线程仍处于阻塞状态。

Q3:binary_semaphore 可以替代 mutex 吗?

不能直接替代。binary_semaphore 的 release() 可以在任意线程调用,而 mutex 要求同一线程 unlock。但正因如此,binary_semaphore 适合实现信号量传递(如生产者通知消费者)。

Q4:counting_semaphore<5> 和 counting_semaphore<10> 在运行时有区别吗?

没有。模板参数只是最大计数的下限声明,运行时行为取决于初始值和 release 调用。不同特化类型的对象也不能相互赋值。

Q5:C++20 的并发原语能替代 std::future 吗?

不能完全替代。latch/barrier 关注线程同步点,future 关注异步结果传递。它们解决的问题正交,常配合使用。

Q6:什么场景下 barrier 比 latch 更适合?

任何需要多轮同步的场景:比如迭代求解(每轮结束后广播中间结果)、帧动画渲染(每帧多线程分工后合并、下一帧继续)。latch 只适合一次性的"等待初始化完成"或"等待所有任务提交"。

七、总结

latch/barrier/semaphore 填补了 C++11 标准库在同步原语上的空白。它们比手写条件变量方案更简洁、性能更好(无虚假唤醒、原子操作更快),并且语义明确、不易出错。在新项目中,凡是遇到"等待 N 个线程"或"限制并发数量"的需求,优先考虑这三个标准原语。

2025 C++并发编程实战:从线程安全到无锁数据结构与协程应用 并发编程是现代软件开发中提升性能的核心技术,其核心原理在于通过多线程或多进程同时执行任务,以充分利用多核CPU的计算能力。从技术价值看,它能显著提高程序的吞吐量和响应速度,尤其适用于高并发服务器、数据处理和实时系统等场景。在C++中,实现并发需要深入理解线程生命周期管理、同步机制和内存模型等基础概念。本文聚焦于C++20/23的最新特性,通过实战案例解析如何构建健壮的线程安全队列,并探讨无锁数据结构与协程的应用。针对常见的性能瓶颈,如锁竞争和缓存伪共享,提供了具体的优化策略和调试方法,帮助开发者从入门到精通 阅读详情

相关推荐

C++多线程同步实战:从互斥锁到原子操作,避坑指南与性能优化

在多线程编程中,线程安全是保障程序正确性的核心挑战。其原理在于协调多个执行流对共享资源的访问,避免数据竞争和竞态条件。这项技术的价值在于充分利用多核处理器性能,构建高并发、高响应的系统。常见的应用场景包括服务器后台处理、高性能计算和实时系统等。本文聚焦于C++中的同步机制,深入探讨互斥锁、条件变量和原子操作等关键工具。通过分析互斥锁的锁粒度优化与死锁避免策略,以及原子操作中内存顺序对性能与正确性的影响,旨在帮助开发者根据实际场景选择合适方案,有效规避线上高并发环境下的数据错乱与程序崩溃风险。

365

C++20实战指南:从概念到应用,掌握现代化C++核心特性

C++作为系统级编程的核心语言,其现代化演进始终围绕提升代码安全性、表达力与性能展开。从编译时计算到并发编程,语言特性的迭代旨在解决工程实践中的痛点。Concepts(概念)通过为模板参数添加编译期契约,从根本上改善了泛型编程的体验,使错误信息更清晰,代码意图更明确。Ranges库引入声明式编程范式,通过惰性求值和管道操作,让数据处理逻辑更直观高效。这些特性不仅提升了开发效率,也为构建高性能、可维护的大型系统提供了坚实基础。在实际应用中,从网络服务到游戏引擎,C++20的新特性正逐步成为现代化项目升级的关键

weixin_33850890的博客 305

雷达原理习题答案【西电】

  目前教材用的是陈老师新版的,但是,我发现课后题有一部分是之前某本书里的。答案如下【建议参看原题→查阅答案】 【原题点这里!!点这里!!】 目录跳转功能目前好像只支持网页版 文章目录% 第一章、第二章% 第三章% 第四章% 第五章% 第六章% 第七章% 第八章 % 第一章、第二章 % 第三章 % 第四章 % 第五章 % 第六章 % 第七章 % 第八章 ...

薛定谔的壳的博客 1万+

CPP-Summit-2020 学习:20 parallelism and concurrency features with heterogenous programming using SY

设计思想独立任务优先消息传递集合并行化以提升吞吐量可变共享状态谨慎使用锁/原子异构计算需要考虑调度与数据传输开销C++ 版本演进C++11 引入线程、lambda、async、atomicC++14 提供 generic lambdaC++17 引入 Parallel STL、execution policies、shared_mutex/locksC++20+ 提供更多异构和并发抽象优化要点尽量减少共享内存和锁使用标准库并行算法提高可维护性。

虾球 968

LSF调度实战:如何用bsub命令实现多队列资源抢占与公平调度

本文深入探讨了LSF作业调度系统的高级实战技巧,重点解析如何通过配置`lsb.queues`文件与灵活运用`bsub -R`命令,构建一套兼顾效率与公平的多队列资源抢占与调度体系。文章详细介绍了队列优先级设计、公平共享机制、资源感知调度以及抢占策略的实施,帮助集群管理员解决高优先级任务等待、资源分配不均等常见问题,实现计算资源的精细化管理和高效利用。

vvv99的博客 218

Silvaco二极管、三极管、CMOS的制备

Silvaco二极管和三极管的制备

yytu231的博客 5749

五、C++ 新特性、关键字与编译原理(进阶)(二)

public:// 类内定义,隐式 inline题号关键词核心结论14重点掌握零拷贝视图、概念约束、范围处理、协程、expected 与 mdspan15STL 选择数据面优先连续内存,控制面按查找和顺序需求选 map/unordered_map16视图借用内存避免拷贝,optional 表达可能缺失17RAII资源绑定对象生命周期,构造获取、析构释放18。

Question_123456的博客 685

【软件系统架构】系列五:TCP/IP 协议栈

TCP/IP协议栈是现代网络通信的核心框架,采用分层架构设计,包含应用层、传输层、网络层和网络接口层四层结构。应用层提供HTTP、FTP等用户服务接口;传输层通过TCP/UDP实现端到端通信;网络层负责IP寻址和路由选择;网络接口层处理物理数据传输。数据在传输过程中经历层层封装,从应用层报文最终转换为物理层的比特流。该协议栈通过三次握手建立连接、数据传输、四次挥手断开连接的流程,确保可靠通信。作为互联网的基础,TCP/IP协议栈具有跨平台、标准化和可扩展的特点,广泛应用于各类网络设备和嵌入式系统中。

moton2017的博客 2164

C++多线程编程实战:从std::thread到线程池与避坑指南

并发编程是现代软件开发的核心概念,它允许程序同时执行多个任务,从而充分利用多核处理器的计算能力,提升程序的响应速度与吞吐量。其基本原理是通过创建多个执行流(线程)来并行处理任务,关键技术包括线程管理、同步与通信。在C++工程实践中,自C++11标准起,语言内置了强大的多线程库,为开发者提供了统一、跨平台的并发工具。掌握多线程技术对于开发高性能服务器、实时系统、游戏引擎及数据处理应用至关重要。本文将深入探讨C++多线程的核心机制,涵盖线程创建与管理、互斥锁与条件变量等同步原语,并针对数据竞争、死锁等常见陷阱提

weixin_33735077的博客 369

RK3399平台开发系列讲解(内核入门篇)1.1、通过sysfs清楚了解设备的系统状况

文章目录1、不敢高声语,恐惊天上人:sysfs目的2、窥一斑而知全豹:sysfs目录介绍3、千丝万缕:sysfs中要素的关系4、数据观星:sysfs中相关结构体 1、不敢高声语,恐惊天上人:sysfs目的 我们想通过sys下面的目录和文件清楚的了解跑Liunx系统的嵌入式设备的系统状况。 2、窥一斑而知全豹:sysfs目录介绍 sysfs把连接在系统上的设备和总线组织成为一个分级的文件,它们可以由用户空间存取,向用户空间导出内核的数据结构以及它们的属性。sysfs的一个目的就是展示设备驱动模型中各组件.

内核笔记 1万+

Mac安装Typescript报错

根本原因是系统级目录的权限限制。推荐通过修改目录权限或使用nvm彻底解决问题。

江拥羡橙的博客 210

深挖C++:类和对象(中)

(先进后出)

xys_678的博客 249

手把手教你学simulink(90.1)--直流电动机实例:基于Simulink的基本模型的直流电动机(DC Motor)建模

直流电动机(DC Motor)因其结构简单、控制方便和良好的调速性能,广泛应用于各种工业自动化、机器人技术、家用电器以及航空航天等领域。为了更好地理解和应用直流电动机的工作原理及其控制系统,本项目旨在通过Simulink对直流电动机进行基本模型的建模与仿真。

MHD0815的博客 440

ThunderKittens 深度解析:一个不基于 MLIR 的 C++ 模板 GPU DSL

Triton 是"让更多人能写 GPU kernel",ThunderKittens 是"让会写的人写得更爽"。前者用 MLIR 换易用性,后者用 C++ 模板换可控性。它们不是替代关系,而是覆盖了 GPU 编程光谱的两端。ThunderKittens 的出现,本质上是对"GPU DSL 必须基于 MLIR"这一默认假设的挑战。编译期类型系统本身就可以是一个强大的 DSL 基础设施,不需要中间 IR,不需要黑盒 pass,也能实现 tile 级抽象和零运行时开销。

Maxwell_Newton的博客 345

机器人视觉仿真软件:PCL (Point Cloud Library)_(8).点云分割技术

点云分割技术在机器人视觉处理中起着至关重要的作用,通过不同的分割方法可以有效地将点云数据中的不同物体或区域分离出来。PCL提供了丰富的分割算法,包括基于平面的分割、基于聚类的分割、基于模型的分割和基于条件的分割等。每种方法都有其适用的场景和优缺点,选择合适的方法可以显著提高点云处理的效率和准确性。希望本节的内容能够帮助读者更好地理解和应用点云分割技术。

kkchenjj的博客 822

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

C++移动构造函数详解

移动构造函数是C++11引入的一种特殊构造函数,用于从临时对象(右值)"窃取"资源来初始化新对象,而不是进行昂贵的深拷贝。char* data;public:// 移动构造函数:参数是右值引用 &&// 关键:把源对象的资源置空。

拒绝AI文,努力在AI时代写出感动人心的好文章 393

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

跟我学c++中级篇—类型定义的方式

本文系统阐述了编程中类型定义的重要性及c++中的实现方式。重点对比了宏定义、typedef与modern using三种机制,指出using在可读性、灵活性和模板支持上的优势,推荐优先使用。同时提醒开发者注意别名滥用、类型歧义、错误信息冗长等问题,强调统一规范与合理设计。最终结论:在现代c++开发中,应以using为主,兼顾简洁性与可维护性,避免陷入命名混乱与编译困境。

fpcc的专栏 86

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

C++ 函数模版

模板参数:● template ,T 就是类型占位符,名字可以自定义,习惯用T● 占位符可以在函数返回值、形参、函数内部当作类型使用使用要点模板代码一般写在头文件:编译器需要看到完整模板源码,才能在每个编译单元完成实例化全局函数、类内成员函数都可以写成模板实例化发生在编译阶段,不是运行时隐式实例化:调用时编译器自动推导实参类型,生成对应版本函数。

2501_94041724的博客 306

C++ 运行时多态与虚函数机制全面剖析【面试重点】

构成C++ 运行时多态必须是基类的指针或者引用调用虚函数。被调用的函数必须是虚函数,并且派生类必须对该虚函数完成重写/覆盖(Override)。只有基类的指针或引用才能既指向基类对象,又指向派生类对象(切片机制);而只有完成了虚函数重写,派生类与基类才具备不同的函数行为,达到“指向谁就调用谁”的效果。在类成员函数的前面加上virtual关键字修饰,该成员函数就成为了虚函数。public:// 成员函数前加 virtual 修饰,声明为虚函数。

Fuyo_0209的博客 447

#SQLite 数据库笔记一:(概念 / 终端指令 / SQL 语句 / C 编程 / 回调函数)

数据库 = 数据的仓库,假设数据是海量。能力说明存储数据持久化保存,程序退出数据还在统计数据、分组、排序数据管理增、删、改、查(CRUD)嵌入式设备上也会用数据库(单片机网关、路由器、工业 HMI 都常见),因为 sqlite 体积小、无需服务进程、单文件即可。数据库 = 库 > 表 > 记录(行) > 字段(列)sqlite3 = 开源、C 写的、单文件、免安装、最多 2T 的嵌入式关系型数据库。

lwq20051110的博客 221

【C++ 基础入门】C++继承(下):问题、底层原理与组合

C++继承(菱形继承的问题、虚继承原理、继承与组合、构造顺序)

2601_96587588的博客 797

蓝桥杯·全国青少年信息素养大赛·电子学会Python/C++考级 备考:莫队算法离线区间查询详解

备赛进行时:第十八届蓝桥杯软件赛报名已于 2026-10-08 10:00 开启、12-18 14:00 截止,省赛定在 2027-04-10(C/C++·Python 程序设计 9:00–13:00);中国电子学会 2026 年 12 月软件编程(C/C++ 一级至十级、Python 一级至六级)考级报名 9/23–12/2 16:00、统考 12-12;全国青少年信息素养大赛 2026—2027 赛季算法应用主题赛也已进入复盘练兵窗口。

IT_Scratch的博客 160

825 mW 顶上 212 °C:ASM491S 的 105 °C/W、250 mA 短路电流与热关断

25 °C 环境里结温 34.1 °C,125 °C 环境里结温 134.1 °C——后者已经离 150 °C 只有 16 °C 了,但还在范围内。换个角度看同一件事:从 25 °C 和从 125 °C 起算,温升都是 86.6 °C,但一个到 111.6 °C(还在范围内),一个到 211.6 °C(远远超限)。这个算例里 20 °C/W 是我假设的,实际值取决于机壳材料、密封程度、有没有风扇,跨度可以从 5 °C/W 到 50 °C/W 以上。外面 50 °C 的机箱,盒内就是 63.9 °C。

ANSILIC的博客 373

《C++ 项目:手写大模型接入 SDK》Gemini接口设计(上):初始化、全量响应实现和测试

本文系统完成了Gemini模型通过OpenAI兼容API的接入开发,基于Apifox验证接口规范,设计并实现GeminiProvider类,涵盖初始化、状态检测、全量响应等核心功能。重点处理了代理配置强制校验、Bearer鉴权、JSON序列化与响应解析等工程细节,并通过GTest与CMake完成单元测试,验证链路可用性。项目遵循策略模式,保持与DeepSeek、ChatGPT等模型的统一抽象接口,显著提升SDK可扩展性。

2501_91275995的博客 395

【容斥原理计数】题解:P9489 ZHY的表示法_容斥原理_计数问题_前缀和问题转化_算法竞赛C++

给定 $ n $ 个正整数,求区间 $[l,r]$ 中有多少正整数 $ x $ 可表示为 $ \sum \lfloor \frac{y}{x_i} \rfloor $。通过二分确定最大 $ y $,转化为求 $ 1 \sim H $ 中是某些 $ x_i $ 倍数的数的个数,利用容斥原理与 DFS 枚举子集计算,结合 LCM 预判剪枝及去重优化,时间复杂度 $ O(2^n \log V) $,可高效处理 $ r \le 10^9 $ 的数据。

YLCHUP的博客 104

头歌 数据结构——C&C++线性表实训

本系列任务围绕线性表的链式实现,依次完成顺序构建、逆序构建、排序插入、查找、删除、求长度及栈、队列、集合等应用。通过动态内存分配与指针操作,掌握单链表的基本操作与实际应用,涵盖插入、删除、遍历、搜索等核心功能,最终实现数据结构在典型场景中的灵活运用。

一名敲代码的苦13程序员 240
上一篇: C++ STL之排列组合与洗牌详解:从使用到底层,再到面试八股
下一篇: C++ STL 之 type_traits 与 Concepts 详解
Ricky_Theseus
博客等级 码龄3年 126粉丝 158原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ricky_Theseus

感谢大家,祝您生活愉快

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值