牛客C++笔记——linux多线程开发

牛客网C++项目-Linux高并发服务器开发之第一章:Linux系统编程入门 学习笔记 自用,不建议阅读,因为内容较为基础且繁杂,更多的是通过观看视频教学留下印象,这篇博文也是为了此后方便查找、补充相关知识点 阅读详情

linux多线程开发

线程概述

在这里插入图片描述

  1. 一个进程可包含多个线程,多个线程之间共享全局内存区
  2. 进程是CPU分配资源的最小单位,线程是操作系统调度执行的最小单位
  3. 使用ps -LF pid就可以查询进程下面的线程

线程和进程的区别

在这里插入图片描述

  1. 线程之间,它们都是共享虚拟地址空间的,只是各自又各自的代码区域,各自又各自的栈空间区域,
    在这里插入图片描述

线程操作函数

在这里插入图片描述

  1. pthread_self()获取当前线程的ID

pthread_create()——创建新线程

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

pthread_exit()——终止线程

在这里插入图片描述

  1. 程序的return 0 或者exit(0) 都是退出了进程,进程都推了,那里面的线程也被销毁了,
  2. 使用pthread_exit() 只是退出了线程,其他线程不受影响
    在这里插入图片描述

pthread_equal()——比较线程ID是否相等

在这里插入图片描述

pthread_jion()——和一个终止的线程进行连接

  1. 用于回收线程的资源
  2. 任何一个函数都可以调用这个去回收别人
  3. 是阻塞的,调用一次只能回收一个
    在这里插入图片描述
  4. 由于每个线程都区分着堆空间,就是局部变量,所以线程返回这里不能是局部变量,下面这样就是错的
    在这里插入图片描述
    得用全局变量返回
    在这里插入图片描述
  5. 主线程中
    在这里插入图片描述

pthread_detach()——分离一个线程

  1. 分离的线程直接就释放资源了,不需要其他线程来回收
  2. 可以直接在主线程中去释放子线程
    在这里插入图片描述

pthread_cancel()——取消一个线程

  1. 就是让线程终止,要终止的话需要一定条件,需要看
  2. 调用之后不是立刻就终止,执行到取消点的时候,才会终止
    在这里插入图片描述

线程属性操作函数

在这里插入图片描述

  1. pthread_attr_ 后面其实有很多函数,可以通过man pthread_attr_ 加tab去查看,这里展示俩
    在这里插入图片描述
    在这里插入图片描述

线程同步

在这里插入图片描述

  1. 临界区可以这样理解,比如1-5行代码,三个线程abc都会执行,我们需要让1-5行代码为原子操作,就是说a在执行的时候,是一口气执行完的,不能说a执行到第2行时候,c把cpu抢过去执行
  2. 像下面的就是不安全的,没有同步
/*
    使用多线程实现买票的案例。
    有3个窗口,一共是100张票。
*/
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
// 全局变量,所有的线程都共享这一份资源。
int tickets = 100;
void * sellticket(void * arg) {
    // 卖票
    while(tickets > 0) {
        usleep(6000);
        printf("%ld 正在卖第 %d 张门票\n", pthread_self(), tickets);
        tickets--;
    }
    return NULL;
}
int main() {
    // 创建3个子线程
    pthread_t tid1, tid2, tid3;
    pthread_create(&tid1, NULL, sellticket, NULL);
    pthread_create(&tid2, NULL, sellticket, NULL);
    pthread_create(&tid3, NULL, sellticket, NULL);

    // 回收子线程的资源,阻塞
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    pthread_join(tid3, NULL);

    // 设置线程分离。
    // pthread_detach(tid1);
    // pthread_detach(tid2);
    // pthread_detach(tid3);
    pthread_exit(NULL); // 退出主线程
    return 0;
}

互斥锁

在这里插入图片描述

  1. 这里用一个例子来理解,就是共用厕所,一个人在上厕所的时候,他就把门锁起来,其他人进不来,只能等着,他用完了,再解锁,其他人再来用
  2. a锁了厕所时,门锁只能由他来开
  3. 在等待锁释放的时候,会睡眠在那里
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    对上面存在问题的代码的改进:
  4. 由于多个线程都需要用到这个锁,所以他需要时全局的
    在这里插入图片描述
  5. 可以在主线程中初始化互斥锁
    在这里插入图片描述
  6. 进程结束前需要释放掉这个锁
    在这里插入图片描述
  7. 给临界区用锁保护起来
    在这里插入图片描述

死锁

在这里插入图片描述

读写锁

在这里插入图片描述

  1. 这样就可以几个线程同时一起读一个全局,效率高于互斥锁
  2. 应用于读取比写数据多的情景下

读写锁操作函数

  1. 用法和互斥锁一样的
    在这里插入图片描述
    在这里插入图片描述

C++11里面引入的管理锁的新类

这里就用到了RALL机制,让锁具有生命周期,锁他的作用域结束的时候,自动释放锁,避免出现如果程序异常,导致没有 unlock;

  1. lock_guard
std::mutex mut;
{
  std::lock_guard<std::mutex> lockGuard(mut);  // lock in lock_guard 构造函数
  sharedVariable++;
}  // unlock in lock_guard 析构函数

上面同样的功能,如果不使用RALL的话

mutex mut;
mut.lock();
sharedVariable++;
mut.unlock();
  1. unique_lock
    类 unique_lock 也是通用互斥包装器,也有lock_guard一样的RAII机制,但它有更多功能,允许延迟锁定、锁定的有时限尝试、递归锁定、所有权转移和与条件变量一同使用。它可移动,但不可复制。它有类似std::mutex一样的接口,更加灵活方便.
std::mutex mut;
// exsample_1{
  std::unique_lock<std::mutex> unilock(mut, std::defer_lock);  // 只创建 unique_lock 对象
  unilock.lock();  // 这里需要手动lock
  sharedVariable++;
}  // 如果里面没有调unlock,这里就unlock

// exsampl_2{
	std::unique_lock<std::mutex> unilock(mut, std::adopt_lock);  // 只创建 unique_lock 对象,已经调用了lock
	sharedVariable++;
	unilock.unlock();  // 这里可以直接解锁,也可以析构解锁
}

  1. 二者都是自释放锁;lock_guard 在时间和空间上都比unique_lock要快;lock_guard 功能单一,只能用作自释放锁;unique_lock具备lock_guard的所有能力,同时提供更多的能力,比如锁的成员函数都会被封装后导出,同时不会引入double lock和 double unlock;

  2. 那么为什么有时候需要unlock()?
    因为lock()锁住的代码段越少,执行越快,整个程序运行效率越高。
    锁头锁住的代码的多少称为锁的粒度,粒度一般用粗细来描述。
    锁住的代码少,这个粒度叫细,执行效率高。
    锁住的代码多,粒度叫粗,执行效率就低。
    要学会尽量选择合适粒度的代码进行保护,力度太细,可能漏掉共享数据的保护,粒度太粗,影响效率。
    选择合适的粒度,是高级程序员的能力和实力的体现。

  3. 如果确定使用unique_lock了,就不要再直接使用 mutex 的 lock 和 unlock ,可以直接使用 unique_lock 的 lock 和 unlock,混合使用会导致程序异常,原因是unique_lock 内部会维护一个标识用来记录自己管理的 锁 当前处于何种状态,如果直接使用 mutex的成员函数,unique_lock无法更新自己的状态,从而导致 double lock 和 double unlock(因为unique_lock一定会在析构的时候unlock),这两种情况都会导致崩溃。

条件变量

在这里插入图片描述

  1. 条件变量就是提供一种在线程之间协调工作的机制,不能用来解决线程数据安全的问题,要和锁配合使用
  2. 比如一个线程使用了pthread_cond_wait(),那他就会阻塞在这,要等到有其他线程通过pthrea_cond_signal()给他发了一个信号之后,才会唤醒他继续往下执行。
  3. 代码示例:
/*
条件变量的类型 pthread_cond_t
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
int pthread_cond_destroy(pthread_cond_t *cond);
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
    - 等待,调用了该函数,线程会阻塞。
int pthread_cond_timedwait(pthread_cond_t	 *restrict cond, pthread_mutex_t *restrict mutex, const struct 				       timespec *restrict abstime);
    - 等待多长时间,调用了这个函数,线程会阻塞,直到指定的时间结束。
int pthread_cond_signal(pthread_cond_t *cond);
    - 唤醒一个或者多个等待的线程
int pthread_cond_broadcast(pthread_cond_t *cond);
    - 唤醒所有的等待的线程
*/
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>

// 创建一个互斥量
pthread_mutex_t mutex;
// 创建条件变量
pthread_cond_t cond;

//创建一个链表用来做生产和消费的容器
struct Node{
    int num;
    struct Node *next;
};

// 头结点
struct Node * head = NULL;

void * producer(void * arg) {
    // 生产者不断的创建新的节点,添加到链表中
    while(1) {
        pthread_mutex_lock(&mutex);
        struct Node * newNode = (struct Node *)malloc(sizeof(struct Node));
        newNode->next = head;
        head = newNode;
        newNode->num = rand() % 1000;
        printf("add node, num : %d, tid : %ld\n", newNode->num, pthread_self());
        // 只要生产了一个,就通知消费者消费
        pthread_cond_signal(&cond);
        pthread_mutex_unlock(&mutex);
        usleep(100);
    }
    return NULL;
}

void * customer(void * arg) {
//消费者,删除一个节点
    while(1) {
        pthread_mutex_lock(&mutex);
        // 保存头结点的指针
        struct Node * tmp = head;
        // 判断是否有数据
        if(head != NULL) {
            // 有数据
            head = head->next;
            printf("del node, num : %d, tid : %ld\n", tmp->num, pthread_self());
            free(tmp);
            pthread_mutex_unlock(&mutex);
            usleep(100);
        } else {
            // 没有数据,需要等待
            // 当这个函数调用阻塞的时候,会对互斥锁进行解锁
            pthread_cond_wait(&cond, &mutex);
            //当收到信号后不阻塞了,继续向下执行,会重新加锁,所以下面需要再解锁一下
            pthread_mutex_unlock(&mutex);
        }
    }
    return  NULL;
}

int main() {
    pthread_mutex_init(&mutex, NULL);//初始化互斥锁
    pthread_cond_init(&cond, NULL);//初始化条件变量

    // 创建5个生产者线程,和5个消费者线程
    pthread_t ptids[5], ctids[5];

    for(int i = 0; i < 5; i++) {
        pthread_create(&ptids[i], NULL, producer, NULL);
        pthread_create(&ctids[i], NULL, customer, NULL);
    }

    for(int i = 0; i < 5; i++) {
        pthread_detach(ptids[i]);
        pthread_detach(ctids[i]);
    }

    while(1) {
        sleep(10);
    }

    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&cond);

    pthread_exit(NULL);
    return 0;
}

信号量

  1. 也称信号灯,像信号灯那么理解,通过灯的亮和灭来交互
    在这里插入图片描述
【linux软件基础知识】std::lock_guard 和 std::unique_lock的区别 总之,std::lock_guard 是一个更简单的锁包装器,它自动获取和释放作用域区域内的锁,而 std::unique_lock 提供了额外的灵活性和功能,例如所有权转移、延迟锁定、定时锁定和条件变量支持。std::lock_guard 是一个简单的包装类,它提供互斥体上的作用域锁。std::lock_guard 和 std::unique_lock 是两个 C++ 标准库类,它们为锁定和管理互斥体提供不同级别的灵活性和功能。另一方面,std::unique_lock 是一个更灵活的锁包装类。 阅读详情

相关推荐

牛客面经整理:C++高频面试题

1.语言基础(C++) (1) 指针和引用的区别 (2) 堆和栈的区别 (3) new和delete是如何实现的,new 与 malloc的异同处 (4) C和C++的区别 (5) C++、Java的联系与区别,包括语言特性、垃圾回收、应用场景等(java的垃圾回收机制) (6) Struct和class的区别 (7) define 和const的区别(编译阶段、安全性、内存占用等) (8) 在...

沉迷单车的追风少年 2487

【C++】Linux多线程开发学习笔记

Linux多线程开发的学习笔记,大部分都由牛客课堂提取出来的,较详细地介绍了多线程开发中需要用到的Linux系统的api。

小黑同学的博客 848

C++ 后端开发路线——愿我踩过的坑,你可以绕着走

阶段0书籍推荐在这个从零起步的阶段我还是建议先从C语言开始学起,不建议直接从C++开始学起,这里我推荐几本我看过的书。《C Primer Plus 第六版》个人非常推荐这本书入门,我入门的时候就是看这本书的第五版,前前后后看了三遍,这三遍不仅仅是读完一遍再读一遍,而是有技巧性的。第一遍可以粗略的读,大概把语法都了解,一些不理解的地方可以自己手动实现一下。第二遍就要精读,课...

编程与实战的博客 3797

linux C++互斥锁std::lock_guard(轻锁)std::unique_lock(重锁)区别、使用{}来控制轻锁lock_guard的生命周期(lock_guard不支持手动解锁)

lock_guard 是基于互斥锁 std::mutex 实现的,unique_lock 是基于通用锁 std::unique_lock 实现的,unique_lock 可以实现比 lock_guard 更灵活的锁操作。但是,在某些情况下,可能需要手动释放互斥锁。它的使用方法很简单,只需要在代码中创建一个 std::lock_guard 对象,并传入一个互斥锁,在它的生命周期内,互斥锁的访问权限将被控制。std::lock_guard 是 C++11 中的标准库,是一个实现互斥锁的简单模板类。

Dontla的博客 8551

Altium Concord Pro 的应用范围,安装,和使用范围

Altium Concord Pro 的应用范围,安装,和使用范围。保证满足两者软件的最低安装要求。 Altium Concord Pro 是配合Digapcb,和MCAD,Altium 和相应机械设计软件的基础和交换桥梁

hanzhaos的博客 1120

SpringBoot SSE避坑指南:当客户端突然断开时服务端SseEmitter的正确回收姿势

本文深入探讨了SpringBoot中SSE连接的生命周期管理,特别是客户端主动断开连接时服务端SseEmitter的正确回收机制。文章重点分析了客户端调用eventSource.cancel()等场景下,如何利用onCompletion()回调进行可靠的资源清理,避免内存泄漏,并提供了防御式编程策略和实战优化建议。

mm9012的博客 778

17.6 unique_lock详解

unique_lock详解

zzyzxb的博客 879

PCRNet: Point Cloud Registration Network using PointNet Encoding

PCRNet算是在ModelNet40这种object-centric数据集上用deep-learning做配准最好入手的一篇文章了,整体架构涉及简洁明了,一句话描述就是简单粗暴,话不多说,直接上网络架构图: Work Flow: 首先对输入source (N×3N \times 3N×3)与template (M×3M \times 3M×3)进行PointNet操作,即MLP + max-pooling后,分别得到描述各自点云的维度为1024的global feature vector:Φ(PS).

dloading7的博客 1647

2022-1-14牛客C++项目 —— 第二章 Linux多线程编程(gdb多进程调试)

复习所用的问题 调试所使用的代码 #include<stdio.h> #include<unistd.h> #include<sys/types.h> int main(void){ printf("begin.\n"); if(fork() > 0){ printf("i am parent process: pid = %d,ppid = %d\n",getpid(),getppid()); int i;

weixin_51187533的博客 252

Kindle使用USB数据线传书封面无法显示问题

不建议按这个解决封面问题,首先USB多次插拔麻烦,其次就是我起初按博主方法没啥问题,后面无论我怎么个顺序都出现了‘暂无图片’,我的建议是在亚马逊退出国内只能USB传书的情况下,下载一个calibre 和。飞行模式下用calibre传书到kindle ,然后用Fix Kindle Ebook Cover 修复一下,后面你要继续飞行模式还是连网随你便,两步完事。2,‘’发送到设备’的时候也最好选择 ‘发送特定格式至’ 选择具体的电子书格式,你的电子书是azw3就选azw3,是mobi就选mobi。

meser88的博客 7418

TC3xx芯片中断配置避坑指南:从AUTOSAR Davinci配置到SRN地址计算的完整流程

本文详细解析了英飞凌TC3xx芯片的中断配置流程,从AUTOSAR Davinci工具链到硬件调试的全链路指南。重点介绍了中断路由(IR)模块、服务请求节点(SRN)和中断控制单元(ICU)的配置技巧,帮助开发者避开常见陷阱,优化中断性能。

weixin_30628801的博客 411

牛客C++项目笔记——第三章 linux多线程开发

【代码】牛客C++项目笔记——第三章 linux多线程开发。

qq_45364037的博客 295

Android双目摄像头(RGB+IR)实时预览与人脸活体检测实战指南

本文详细介绍了Android双目摄像头(RGB+IR)实时预览与人脸活体检测的实战指南。通过双摄同步预览、人脸检测与活体判断等技术实现,提升安全认证的准确性和鲁棒性。文章还提供了开发环境搭建、常见问题解决方案及性能优化建议,帮助开发者快速掌握双目摄像头在Android平台的应用。

vulkan6gpu的博客 691

牛客C++笔记——linux多进程开发

c++笔记——linux多进程开发

qq_42657656的博客 1020

YOLO 线性小目标涨点|3890 张输电导线散股 VOC+YOLO 单类别数据集实战,无人机电力缺陷巡检全流程落地

架空输电导线是电网能量传输核心载体,长期受风力摆动、雷击、腐蚀、外力撞击作用,外层铝股极易出现断裂散股故障。导线散股初期仅少量铝丝开裂,肉眼与无人机远距离航拍下属于细长微弱线性小目标;若未及时发现,散股会持续扩张,引发导线过热、断线、大面积停电重大电力安全事故。传统人工巡检存在多重行业痛点:山区、跨江河线路人员无法攀爬登杆,高空作业人身坠落风险极高;无人机单次航拍产出数千张高清图片,人工逐帧筛查微小散股耗时 3-5 天,阴天、逆光、植被遮挡场景漏检率超 45;

lin_xiu的博客 52

WinCC实现定时存储数据至SQLServer

环境 Win10 WinCC V7.5 SQL Server 2016 运行结果 流程 WinCC变量 全局VB脚本 (我设置的是每2秒存一次,因此周期性触发器设置1秒)

skr? 3987

cpp学习计划

内容包括:✅ 本周完成任务❌ 卡住问题💡 下周优化方向🧩 一条“可写进简历”的技术积累点每完成一个项目 → 立刻写:README(项目架构图 + 性能测试截图)技术博客(例如「我用C++实现了一个高性能HTTP服务器」)

qq_59116228的博客 1632

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

牛客C++项目笔记—第一章linux入门

库文件是计算机上的一类文件,可以简单的把库文件看成一种代码仓库,它提供给使用者一些可以直接拿来用的变量、函数或类。- 库是特殊的一种程序,编写库的程序和编写一般的程序区别不大,只是库不能单独运行。- 库文件有两种,静态库和动态库(共享库),区别是:静态库在程序的链接阶段被复制到了程序中;动态库在链接阶段没有被复制到程序中,而是程序在运行时由系统动态加载到内存中供程序调用。- 库的好处:1.代码保密 2.方便部署和分发。

qq_45364037的博客 444

android 蓝牙通讯demo

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

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

Linux高并发服务器开发---笔记1(环境搭建、系统编程、多进程)

0613首先这整个系列笔记属于笔记①:牛客校招冲刺集训营—C++工程师中的第四章笔记。视频课链接: 视频1:Linux高并发服务器开发(40h); 视频2:第4章 项目制作与技能提升(录播)(26h30min); 视频课3: 第5章 高频考点与真题精讲(录播)中的5.10-5.13 项目回顾有个学生的评论: 上个月做了也是这个老师讲得一个2400分钟的webserver课程,但只能实现访问服务器上图片的功能,也没有登录,数据库日志相关的东西,现在又看到这个课程,人麻了,先做这个多好。。。所以直接看这个26h

weixin_38665351的博客 8699

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

Linux C++服务器项目——项目实战2、3(项目代码讲解)

但反过来思考,注册了EPOLLONESHOT事件的socket一旦被某个线程处理完毕,该线程就应该立即重置这个socket 上的EPOLLONESHOT事件,以确保这个socket下一次可读时,其EPOLLIN事件能被触发,进而让其他工作线程有机会继续处理这个socket。状态机的当前状态记录在cur_State变量中。比如一个线程在读取完某个socket 上的数据后开始处理这些数据,而在数据的处理过程中该socket上又有新数据可读(EPOLLIN再次被触发),此时另外一个线程被唤醒来读取这些新的数据。

程序员,他们想的是什么?他们想的永远都是技术,他们崇尚的也永远都是技术。 1146

C/C++服务器开发/后端开发 学习、面试总结 经验分享

C/C++发展方向 后台/服务器 桌面客户端 图形学/游戏/VR/AR 测试开发 网络安全/逆向 嵌入式/物联网 音视频/流媒体/SDK 计算机视觉/机器学习 什么是后端/后台/服务器开发? 经常在各大公司招聘上看到后端、后台、服务器开发等等,有些同学经常被这些名词搞混。 其实这些名词都是相近的,但是也有点区别,这里说说我的理解: 首先一般公司分为前端和后端,前端就是和用户打交道的,负责用良好的视觉效果将数据呈现给用户,广义的前端包括客户端(安卓、IOS)、Web前端、小程序等。 而与之对应的后端则是负

C/C++Linux、音视频、DPDK 5814

牛客Linux高并发服务器开发学习第一天

安装Xshell 7远程连接虚拟机的Ubuntu安装Xftp 7可以传输文件(暂时还没使用)安装VMware Tools可以直接从Windows系统向虚拟机Linux系统拖拽文件实现文件交互。安装CScode可以远程连接Linux系统进行代码的编写。(Windows系统与Linxu系统公钥进行了一系列操作使得CScode直接连接Linux系统不需要输入密码)

jinyang1110的博客 546

[cpp]Linux系统编程(到进程)

牛客webserver笔记

记忆超差的本人的博客 1915

牛客C++项目笔记——第二章linux多进程开发

exec 函数族的作用是根据指定的文件名找到可执行文件,并用它来取代调用进程的内容,换句话说,就是在调用进程内部执行一个可执行文件。- exec 函数族的函数执行成功后不会返回,因为调用进程的实体,包括代码段,数据段和堆栈等都已经被新的内容取代,只留下进程 ID 等一些表面上的信息仍保持原样,颇有些神似“三十六计”中的“金蝉脱壳”。看上去还是旧的躯壳,却已经注入了新的灵魂。只有调用失败了,它们才会返回 -1,从原程序的调用点接着往下执行。

qq_45364037的博客 567

牛客项目-linux开发环境搭建

安装vmware 新建虚拟机,安装vmware-tools。 (此时不能拖拽文件,复制粘贴) sudo apt-get autoremove open-vm-tools sudo apt-get install open-vm-tools sudo apt-get install open-vm-tools-desktop 执行以上命令后,即可拖拽,复制粘贴。 配置下xshell 6: sudo apt install openssh-server 安装ssh sudo apt instal

雪招在等开花 1588

牛客C++笔记——linux网络编程

linux网络编程

qq_42657656的博客 507
上一篇: C++笔记——linux系统编程入门
下一篇: 牛客C++笔记——linux网络编程
MUCH_WAY
博客等级 码龄8年 25粉丝 26原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值