基于C++11/14/17的线程池实现

<C++> 基于C++11/14/17的线程池实现 线程池,顾名思义就是预先启动一些线程,集中管理,需要的时候直接拿来用,无需用时再创建。尤其是在Windows平台,线程是稀缺资源,线程的创建和销毁都是十分耗时的,所以利用线程池来提升并发场景下的性能,是十分有必要的。 C++11首次对并发进行了支持,这使得我们利用STL编写多线程应用程序成为了可能,不过STL的thread比较简陋,并没有提供更多的强大特性,只是一个最基础的多线程解决方案,定位应该是 阅读详情

线程池,顾名思义就是预先启动一些线程,集中管理,需要的时候直接拿来用,无需用时再创建。尤其是在Windows平台,线程是稀缺资源,线程的创建和销毁都是十分耗时的,所以利用线程池来提升并发场景下的性能,是十分有必要的。

C++11首次对并发进行了支持,这使得我们利用STL编写多线程应用程序成为了可能,不过STL的thread比较简陋,并没有提供更多的强大特性,只是一个最基础的多线程解决方案,定位应该是尽可能面向更多应用场景的最通用的版本。

先上代码:Thread Pool 
为了拥抱新标准,尽量使用了C++14/17的特性,所以如果需要编译运行这些代码的话,需要启用你的编译器的c++17特性。

本文不对thread,mutex,condition_variable作过多解释,不知道的同学可以去查阅相关资料 ——> cpp_reference。

首先我们定义设计ThreadPool这个类的接口:

class ThreadPool {
    public:
        explicit ThreadPool(const size_t& max_threads);

        template<class Func, typename... Args>
        decltype(auto) submitTask(Func&& func, Args&&... args);
        void pause();
        void unpause();
        void close();
        bool isClosed() const;
        ~ThreadPool();
    protected:
        void _scheduler();
        void _launchNew();
    private:
        static size_t core_thread_count;
        size_t max_thread_count;
        // thread-manager
        std::vector<std::thread> threads;
        // tasks-queue
        std::queue<std::function<void()>> tasks;
        // for synchronization
        std::mutex queue_mtx;
        std::mutex pause_mtx;
        std::condition_variable cond_var;
        bool paused;
        bool closed;
    };
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

我参考了部分Java中线程池的设计方式,有core_thread_count来指定最先启动的基础工作线程数,max_thread_count来指定最多运行的工作线程数,通常被传入构造函数的整数max_threads指定,如果max_threads比core_thread_count还小,那么指定max_thread_count=core_thread_count.以下是完整的构造函数:

thread_pool::ThreadPool::ThreadPool(const size_t & max_threads)
    : closed(false), paused(false), max_thread_count(max_threads)
{
    if (max_threads <= 0) {
        max_thread_count = core_thread_count;
        throw std::runtime_error("Invalid thread-number passed in.");
    }
    size_t t_count = core_thread_count;
    if (max_threads < core_thread_count) {
        max_thread_count = core_thread_count;
        t_count = max_threads;
    }
    // launch some threads firstly.
    for (size_t i = 0; i < t_count; ++i) {
        _launchNew();
    }
    // lanuch sheduler and running background.
    std::thread scheduler = std::thread(&ThreadPool::_scheduler, this);
    scheduler.detach();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

接下来解析两个关键的函数:_scheduler和_launchNew。 
_launchNew函数用于启动一个工作线程,具体实现如下:

void thread_pool::ThreadPool::_launchNew()
{
    if (threads.size() < max_thread_count) {
        threads.emplace_back([this] {
            while (true) {
                if (this->paused) {
                    std::unique_lock<std::mutex> pause_lock(this->pause_mtx);
                    cond_var.wait(pause_lock, [this] {
                        return !this->paused;
                    });
                }
                std::function<void()> task;
                {
                    std::unique_lock<std::mutex> lock(this->queue_mtx);
                    cond_var.wait(lock, [this] {
                        return this->closed || !this->tasks.empty();  // trigger when close or new task comes.
                    });
                    if (this->closed)  // exit when close.
                        return;
                    task = std::move(this->tasks.front());
                    this->tasks.pop();
                }
                task();  // execute task.
            }
        }
        );
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

最核心的是while(true)体内的部分。 
这是一个无限循环的函数体,除非线程池被关闭,否则它会一直的运行,首先通过condition_variable::wait使其进入休眠,当队列中有新任务加入时,会有condition_variable::notify_one唤醒此线程,然后它从中取出队首的任务,此处是一个void()类型的可调用对象(经过lambda包装,后面会讲),即function<void()>,取出后执行任务,执行结束继续进入休眠状态(也可能会遇到线程池暂停,具体的可以自己看代码)。

_scheduler()是线程的调度函数,实现如下:

void thread_pool::ThreadPool::_scheduler()
{
    // find new task and notify one free thread to execute.
    while (!this->closed) {  // auto-exit when close.
        if (this->paused) {
            std::unique_lock<std::mutex> pause_lock(this->pause_mtx);
            cond_var.wait(pause_lock, [this] {
                return !this->paused;
            });
        }

        if (tasks.empty() ||
            tasks.size() > max_thread_count)  // if tasks-size > max_threads , just loop for waiting.
            continue;
        else if (tasks.size() <= threads.size())
            cond_var.notify_one();
        else if (tasks.size() < max_thread_count) {
            _launchNew();
            cond_var.notify_one();
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

也是一个无限循环的while结构,当队列中有任务时,根据任务数量,通知已有的线程去取任务执行,或者增加线程数量,继续通知。

最最重要的是submitTask()这个函数,它是整个线程池的核心,也是任务提交到线程池内的唯一接口,这里用到了decltype(auto),init capture,std::apply,std::make_tuple等C++14/17新特性,当然还有c++11加入的universal reference等,就不一一赘述了,先上具体实现:

template<class Func, typename... Args>
    inline decltype(auto)
        ThreadPool::submitTask(Func&& func, Args&&...args)
    {
            auto task = std::async(std::launch::async,
                std::forward<Func>(func), std::forward<Args>(args)...);
            return task;
        }

        using return_type = typename std::result_of_t<Func(Args...)>;

        auto task = std::make_shared<std::packaged_task<return_type()>>(
            [func = std::forward<Func>(func),
            args = std::make_tuple(std::forward<Args>(args)...)]()->return_type{
            return std::apply(func, args);
        }
        );

        auto fut = task->get_future();
        {
            std::lock_guard<std::mutex> lock(this->queue_mtx);
            if (this->closed || this->paused)
                throw std::runtime_error("Do not allow executing tasks after closed or paused.");
            tasks.emplace([=]() {  // `=` mode instead of `&` to avoid ref-dangle.
                (*task)();
            });
        }
        return fut;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

第一个参数是一个可调用对象,后面的变长模板参数是这个可调用对象的参数,使用通用引用配合std::forward就可以达到完美转发任意函数及其参数的作用。因为我们在执行任务的时候,调用的统一是std::function<void()>对象,所以我们要把真正的函数用一个无形参无返回值的lambda包装,而任务的执行结果用std::future可以得到,所以我们先构建std::packaged_task,然后通过它得到该任务的future对象,它用于最后的返回,我们可以通过对返回后的它调用.get()得到任务的运行结果,包装好的packaged_task则用无返回值无形参的lambda包装之后,加入队列。

其它的暂停/关闭这些小接口的实现就自己看源码吧~ 
然后做了个简单的性能测试(代码在example.cpp,经人提醒去掉了一些可能会影响结果的噪声):

  • 创建线程 -> 执行任务 -> 销毁线程
  • 创建线程池 -> 把任务丢到线程池里

在VS-Run模式下,性能差距非常明显: 
如图: 
method_one

Windows平台下使用clang++编译运行,两者的性能差距与上图接近。

Linux下使用最新的clang++ 5.0除了需要添加-std=c++17,还需要添加-lpthread参数,但是我编译好运行的时候直接被bash给杀了…也是很无奈。

差不多就是这样,一个积极拥抱新标准的线程池实现,后续准备将std::queue用无锁队列替换,可以进一步提高性能。

觉得有帮助的可以给个Star.

从零构建C++异步框架:深入解析任务队列与线程池设计 并发编程是现代软件开发的核心技术,旨在通过多线程并行执行任务来提升程序性能。其基本原理在于将计算任务分解为独立单元,由操作系统或运行时环境调度到多个CPU核心上同时执行,从而充分利用多核硬件资源。在C++等系统级语言中,实现高效并发需要精细控制线程生命周期与任务调度,以避免资源竞争与性能瓶颈。异步框架与任务队列正是解决这一问题的关键技术,它们将任务提交、排队、调度与执行解耦,对外提供简洁的异步API,对内实现资源的统一管理与高效复用。这种模式在游戏服务器、实时数据处理、高并发网络服务等场景中价值显著,能有效 阅读详情

相关推荐

C++并发编程实战:从进程线程原理到线程池实现

并发编程是现代软件开发的核心技术,旨在通过同时执行多个任务来提升程序性能。其基本原理是利用进程和线程两种抽象模型,进程提供资源隔离,线程作为轻量级执行单元共享进程资源。在C++中,标准库提供了std::thread、std::async等工具实现并发,而线程同步则依赖互斥锁、条件变量和原子操作来确保数据安全。这项技术的核心价值在于充分利用多核CPU计算能力,显著提高吞吐量和响应速度,广泛应用于高性能服务器、数据处理和实时系统等场景。本文聚焦C++并发编程实战,深入解析进程与线程的核心概念,并通过构建线程池展

weixin_32576389的博客 227

C++11 14 17 20 多线程从原理到线程池实战

学习计划 每天学习一小时以上 跟着视频动手编写代码 调试代码并对比课程多提供的源码 课程目标 理解多线程原理并学会c++11 的多线程编程 理解线程池技术原理并能使用c++实现 理解c++11 14 17 20 多线程编程相关特性 常见问题 课程使用的开发工具? 课程使用的开发工具是vs2019 课程代码是否是跨平台? 课程代码都是基于c++自生特性,没有用到第三方库,都是跨平台 旧版本的开发工具是否可以? 课程中的大部分代码是c++11 ,绝大部分开发工具都支持,其中c++14 17要比较新的工具支持,c++20需要vs2019 课程是否提供源码? 课程提供源码,方便同学学习过程的出现问题,进行对照调试错误。   

现代C++并发编程实战:从多线程到线程池的完整指南

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

djai0102的博客 390

详解C++17线程池的实现

C++标准库在C++11之后新增了线程特性,C++的线程在此之后可以进行跨平台使用。在其他的OOP语言(例如Java、C#等)中有线程池来避免过多过频创建线程,提升了不少的性能,在网络库中也有用到线程池的情况,因此了解一个线程池的实现可以更加熟悉多线程的使用。本文将用C++17实现一个简单的线程池,github地址:https://github.com/Invictus2/Thread...

人世如潮人如水的博客 5016

手把手教你用Modern C++重写TinyWebServer:从Pthread到std::thread的实战迁移

本文详细介绍了如何使用Modern C++(C++11/14/17)重写TinyWebServer,从传统的Pthread迁移到std::thread的实战过程。通过RAII、智能指针和标准库线程组件,提升代码的安全性、可维护性和性能,适合希望掌握现代C++并发编程的开发者。

weixin_30333885的博客 257

告别命令行!用这个开源的Web版Kettle工具,5分钟搞定数据集成任务编排

本文介绍了一款开源的Web版Kettle工具,通过直观的拖拽界面大幅提升数据集成效率,告别复杂的命令行操作。该工具基于Kettle核心引擎,支持跨平台协作、轻量化访问和降低学习曲线,特别适合业务分析师快速上手。实测显示,使用该工具后任务配置错误率下降62%,协作效率提升40%。

weixin_30267785的博客 433

C++17 线程池类

线程池是一种池化资源技术,也是一种多线程模式。线程池调度线程执行任务,通常序列化任务,形成任务队列。 调度的关键在于条件变量。当不满足执行条件时,阻塞线程以让出处理器,在满足条件之前,不占用处理器,以提高资源利用率。 线程池提供设置容量方法,由守护线程异步增删工作线程。当任务队列非空时,一次性增加工作线程;当存在闲置工作线程时,逐个删减工作线程。

独孤过的博客 3344

还在用iReport 5.6.0?手把手教你用JasperSoft Studio 6.8.0设计报表(附JDK 11+兼容方案)

本文详细介绍了从iReport 5.6.0迁移到JasperSoft Studio 6.8.0的全过程,包括JDK 11+环境配置、项目迁移实战、高级特性应用及常见问题解决方案。帮助开发者高效完成报表生成工具的升级,提升开发体验和报表性能。

weixin_30687051的博客 407

C++17 线程类

线程类提供线程重用方案,支持销毁再创建,一次创建反复使用。 线程类灵活性高,既作为独立模块,也作为线程池的线程,既可以配置单任务,也可以配置任务队列。 线程类支持配置任务队列,可以实现单线程独占任务队列,按照出队列之顺序执行任务;也可以实现多线程共享任务队列,不过需要确保队列接口线程安全。

独孤过的博客 4548

【simulink模型】独立悬架振动模型,七自由度整车simulink模型

车辆悬架系统的设计对提升车辆行驶平顺性和操纵稳定性具有重要意义。本文构建了独立悬架的振动仿真模型以及七自由度整车动力学模型,基于 Simulink 平台实现对模型的动态响应仿真。通过对比不同工况下的车辆振动响应,验证了模型的准确性与实用性。研究结果为车辆悬架系统的优化设计提供了有力支持。

2401_84610415的博客 1581

利用ADS设计射频功率放大器

利用ADS设计射频功率放大器 任务: 用飞思卡尔公司的功率放大器芯片ATF27s006NT1设计射频功放,芯片频率范围可达450MHz-3.6GHz。 功放指标: (1) 工作频率:500MHz (2) 增益:G>20dB (3) 电源:+28V (4) 最大输出功率:2W(33dBm) 设计思路 (1)安装DesignKit模型; (2)直流DC扫描; (3)稳定性分析; (4)偏置电路设计及整...

UVM频率 3万+

深度解析:C++11线程池与SafeQueue的高效实现实战指南

thr/thread-pool是一个基于C++11标准库的线程池实现,通过SafeQueue线程安全队列实现高效的任务调度。本文将从并发编程的实际问题出发,深入剖析线程池的核心设计思想、线程安全队列的实现原理,以及如何在实际项目中应用这一高效的多线程解决方案。 ## 多线程编程的核心挑战与解决方案 在现代并发编程中,开发者常常面临两大难题:**数据竞争**和**资源管理**。想象一下,多个线程

gitblog_00713的博客 449

新手避坑指南:手把手教你从零编写DC综合的.synopsys_dc.setup配置文件

本文详细介绍了如何从零开始编写DC综合的.synopsys_dc.setup配置文件,帮助新手避开常见误区。内容涵盖工艺库路径设置、核心参数详解、实用调试技巧以及高效配置模板,特别强调了配置文件在数字IC设计流程中的重要性。通过实战案例和常见报错解决方案,读者可以快速掌握DC综合配置的关键技能。

weixin_30517215的博客 296

C++11/14/17多线程和线程池(万字)

是指在一个进程(程序的运行实例)中同时创建和运行多个,这些线程共享进程的内存空间(如全局变量、堆内存),但拥有独立的执行路径(程序计数器、栈空间)。简单来说,多线程允许程序 “同时” 执行多个任务。多线程的核心价值如下:(1)提高CPU使用率:对于多核CPU,单线程程序只能利用一个核心,其他核心处于空闲状态(2)实现 “并发响应”:多线程可将耗时操作放在后台线程,主线程保持响应。(3)拆分复杂任务:多线程可按功能拆分任务,每个线程负责一部分,简化代码逻辑。

weixin_45859446的博客 855

用OpenCV+ROS实现焊接机器人智能纠偏:线激光视觉的算法调参全流程

本文详细介绍了基于OpenCV和ROS的焊接机器人智能纠偏系统全流程。通过线激光视觉系统实时感知焊缝,重点解析了抗干扰图像预处理、改进的Steger算法提取激光条纹中心线,以及从2D像素到机器人基坐标系的完整坐标变换。该系统为应对焊接飞溅、弧光等复杂工况,实现高精度实时路径纠偏提供了从算法优化到工程集成的完整解决方案。

vodka的博客 157

用limma包进行多组差异表达分析

写在前面:最近在使用limma包进行差异表达分析,参考了网上许多教程都觉得说的云里雾里,很不清楚。经过我自己一段时间非常痛苦的钻研,弄明白了,解决了我的实际需求。于是决定将我的分析经验写下来,分享给需要的人。 首先加载前期预处理好的表达矩阵。(我的原始表达矩阵文件已附在文后,大家有需要可以下载实践) setwd("E://Rworkplace") data<-read.table...

weixin_40640700的博客 4万+

ThreadPool.h与C++标准:符合C++11及以上标准的实现

你是否还在为C++多线程编程中的线程管理、任务调度和同步机制而烦恼?是否在不同编译器和平台间挣扎于线程API的兼容性问题?本文将深入剖析ThreadPool.h如何充分利用C++11标准特性,实现一个高效、可移植且符合现代C++规范的线程池(Thread Pool)。读完本文,你将掌握: - C++11核心并发特性在实际项目中的应用 - 线程池实现中的关键设计模式与最佳实践 - 如何编写符合C+...

gitblog_00101的博客 416

IEC61850建模实战:从标准解读到ICD文件生成

本文详细介绍了IEC61850建模的实战过程,从标准解读到ICD文件生成,涵盖了变电站自动化系统的结构化数据模型设计。通过分层结构、标准服务映射和XML文件解析,帮助读者掌握电力系统智能电子设备的标准化建模方法,提升工程实施效率。

weixin_30823001的博客 413

c++完美线程池

以下是一个结合现代C++特性和工业级最佳实践的线程池实现,支持任务返回值、优雅关闭、异常处理和高效资源管理146cpp。

qq_62383467的博客 415

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

C++ 学习规划

C++ 学习规划(导向)本扎实的 C++ 基础 + 面向对象编程思维 + 熟练掌握 STL(容器、算法、迭代器) + 熟悉多线程编程。目标是达到中高级工程师面试水平(字节、腾讯、阿里、华为等大厂 C++ 岗位常见要求),兼顾代码能力和系统理解。规划分为 6 个阶段,建议 3-6 个月 完成(每天 2-4 小时),可根据个人基础灵活调整。每个阶段包含学习目标、核心内容、练习要求、推荐资源。目标:掌握现代 C++(C++11/14/17/20)语法和编程习惯,避免写“传统 C 风格”代码。核心内容:练习:推荐资

专注于前沿技术的探索与实践,热衷于从代码逻辑、架构设计到行业应用等多维度深挖技术本质。期待在技术之路上与你共同成长~ 785

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

C++进阶项目:工业级多任务队列设计与实现

调用 notifyWake(),最小化时间窗口。,会导致任务延迟执行甚至挂起。代码语言:javascript。代码语言:javascript。代码语言:javascript。代码语言:javascript。代码语言:javascript。代码语言:javascript。代码语言:javascript。代码语言:javascript。代码语言:javascript。代码语言:javascript。代码语言:javascript。代码语言:javascript。代码语言:javascript。

2509_93923260的博客 224

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

C++可变参模板线程池:从类型安全任务提交到工业级实现

线程池是多线程编程中的核心组件,它基于生产者-消费者模型,通过任务队列和工作线程池来管理并发任务的执行。其核心原理在于将任务提交与执行解耦,有效避免线程频繁创建销毁的开销,提升系统资源利用率和响应性能。在C++中,实现一个通用、类型安全的线程池需要运用可变参模板和完美转发技术,这不仅能支持任意可调用对象和参数组合,还能通过std::future提供异步结果获取和异常传播机制,极大增强了代码的灵活性和健壮性。这类通用线程池在需要处理多种异步计算场景中价值显著,例如高并发网络服务、数据分析模块或实时计算引擎,能

weixin_30876945的博客 425

C++ Concurrency in Action 2ed代码实现原理:并发编程核心组件深度分析

C++ Concurrency in Action 2ed是一本深入探讨C++11/14/17/20并发编程的经典书籍,通过实例代码展示了如何构建线程安全的并发程序。本文将深度剖析书中核心并发组件的实现原理,帮助开发者理解并发编程的关键技术和最佳实践。 ## 一、并发状态管理:从ATM机模型看状态机设计 在并发编程中,状态管理是确保线程安全的基础。书中通过ATM机的状态转换模型,展示了如何使用

gitblog_00165的博客 854

用Modern C++重写TinyWebServer:告别PIXIS风格,拥抱C++11线程库的实战心得

本文详细介绍了如何用Modern C++11/14/17特性重构TinyWebServer,从传统的Pthread实现迁移到STL线程库。通过对比分析,展示了现代C++在资源管理、类型安全和代码简洁性方面的优势,并提供了线程池、连接管理等核心组件的现代化改造方案,帮助开发者构建更高效、安全的Web服务器。

weixin_30644369的博客 404

lambda和thread以及线程池

3、std::thread有一个模板构造函数,可以接受函数对象,而lambda是匿名函数对象,此外,emplace_back直接在容器内存中构造对象,相比push_back避免了额外的复制/移动。1、lambda表达式本质上是函数对象的语法糖,是一个匿名的函数对象。当写一个lambda表达式时,编译器实际上会生成一个匿名类.2、通过函数包装器可以将lambda转为函数对象。(2)完整版,支持动态调整和任务优先级。4、线程池的基础版、完整版、现代版。(1)基础版C++11/14。

zhangjian1234567890的专栏 245
上一篇: C++11多线程基本使用
下一篇: C++11std::unique_lock与std::lock_guard的区别及多线程应用实例
imJaron
博客等级 码龄11年 121粉丝 286原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值