Valgrind 并发调试 :DRD 实战—死锁与锁序一网打尽

Valgrind的使用 在上述代码中,`arr` 分配了 10 个 `int` 的空间,索引范围应为 `0` 到 `9`。- by 0x4005C9: main (example.c:6):内存是由 `example.c` 文件的第 6 行 `main` 函数分配的。通过上述信息,可以清晰地看到内存泄漏的来源,即 `main` 函数中的第 6 行调用了 `malloc`,但未调用 `free` 释放内存。- `--track-origins=<yes|no>`:追踪未初始化值的来源,`yes` 会增加检测准确性,但会降低性能。 阅读详情

📖 针对Valgrind 并发调试 :同步B站的视频课程讲解



Valgrind 并发调试 · Part 2:DRD 实战——死锁与锁序一网打尽

目标:用最小可复现示例,系统讲清 DRD 如何发现死锁、锁顺序反转与常见锁误用;给出“能直接跑”的命令、输出解读与两种标准修法。


在这里插入图片描述

1. DRD 是什么?什么时候选它

DRD (Thread Error Detector) 是 Valgrind 的并发诊断器之一,特点:

  • 轻量、速度快:更适合常规巡检或 CI 回归。
  • 对锁序问题直观:会统计大量具体冲突实例(冲突密度)。
  • 覆盖范围:数据竞争、死锁/锁序反转、条件变量误用、线程 API 误用等。

小结:先 Helgrind 清理 data race,后 DRD 量化锁冲突与复现死锁,两者互补。


2. 必备概念速览

  • 互斥量 (mutex):同一时刻仅允许一个线程进入临界区。
  • 死锁 (deadlock):两个(或更多)线程彼此等待对方持有的资源而永不前进。
  • 锁顺序反转 (lock order inversion):两条路径对多把锁采用相反顺序加锁,极易形成环路等待。
  • 形成死锁的四要素:互斥、占有且等待、不剥夺、循环等待(命中这四个就会死锁)。

3. 最小示例:两把锁、相反顺序(偶发死锁)

来自你的代码,加入微小延时扩大竞态窗口。

// deadlock.c
// gcc -O0 -g deadlock.c -pthread -o deadlock
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>

static pthread_mutex_t m1 = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t m2 = PTHREAD_MUTEX_INITIALIZER;

void* thread_A(void* arg) {
    pthread_mutex_lock(&m1);               // A 先拿 m1
    usleep(1000);                          // 放大竞争窗口
    pthread_mutex_lock(&m2);               // 再等 m2
    printf("A got m1 -> m2\n");
    pthread_mutex_unlock(&m2);
    pthread_mutex_unlock(&m1);
    return NULL;
}

void* thread_B(void* arg) {
    pthread_mutex_lock(&m2);               // B 先拿 m2(与 A 相反)
    usleep(1000);
    pthread_mutex_lock(&m1);               // 再等 m1
    printf("B got m2 -> m1\n");
    pthread_mutex_unlock(&m1);
    pthread_mutex_unlock(&m2);
    return NULL;
}

int main(void) {
    pthread_t tA, tB;
    pthread_create(&tA, NULL, thread_A, NULL);
    pthread_create(&tB, NULL, thread_B, NULL);
    pthread_join(tA, NULL);
    pthread_join(tB, NULL);
    return 0;
}

运行 & 现象

gcc -O0 -g deadlock.c -pthread -o deadlock
./deadlock         # 可能卡死,也可能“偶然成功”
  • 若发生死锁:程序卡住不退出。
  • 若未死锁:输出两行 A got ... / B got ...,但这只是非确定性调度带来的侥幸。

用 DRD 检查

valgrind --tool=drd -q ./deadlock
  • 可能打印 0 错误(当次未形成环路),也可能给出锁序冲突/互斥量等待的相关提示。关键是:

    • 反复运行可观察冲突实例数量(ERROR SUMMARY: N errors from M contexts)。

4. 让死锁“必现”:同步栅栏对齐(强制构造循环等待)

用 pthread_barrier_t 让 A/B 各自拿到第一把锁后同时去抢第二把。

// deadlock_barrier.c
// gcc -O0 -g deadlock_barrier.c -pthread -o deadlock_barrier
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>

static pthread_mutex_t m1 = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t m2 = PTHREAD_MUTEX_INITIALIZER;
static pthread_barrier_t bar;

void* thread_A(void* arg) {
    pthread_mutex_lock(&m1);
    pthread_barrier_wait(&bar);    // 等 B 锁住 m2
    pthread_mutex_lock(&m2);       // 与 B 形成环路等待 → 死锁
    return NULL;
}

void* thread_B(void* arg) {
    pthread_mutex_lock(&m2);
    pthread_barrier_wait(&bar);    // 等 A 锁住 m1
    pthread_mutex_lock(&m1);       // 与 A 形成环路等待 → 死锁
    return NULL;
}

int main(void) {
    pthread_barrier_init(&bar, NULL, 2);
    pthread_t tA, tB;
    pthread_create(&tA, NULL, thread_A, NULL);
    pthread_create(&tB, NULL, thread_B, NULL);
    pthread_join(tA, NULL);
    pthread_join(tB, NULL);
    pthread_barrier_destroy(&bar);
    return 0;
}

DRD 下的直观表现

valgrind --tool=drd ./deadlock_barrier
  • 程序会卡住;用 Ctrl+C 结束后,DRD/Helgrind 往往会报告线程退出仍持锁或锁顺序反转。
  • DRD 的优势:会把大量冲突实例与访问段统计出来,便于评估冲突密度。

5. 两种标准修法(务必掌握)

5.1 统一加锁顺序(首选)

全项目约定:永远先 m1 再 m2。

// fixed_order.c
// gcc -O0 -g fixed_order.c -pthread -o fixed_order
#include <pthread.h>

static pthread_mutex_t m1 = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t m2 = PTHREAD_MUTEX_INITIALIZER;

static inline void lock_in_order() {
    pthread_mutex_lock(&m1);
    pthread_mutex_lock(&m2);
}
static inline void unlock_in_order() {
    pthread_mutex_unlock(&m2);
    pthread_mutex_unlock(&m1);
}

void* A(void* arg){ lock_in_order(); /* 临界区 */ unlock_in_order(); return NULL; }
void* B(void* arg){ lock_in_order(); /* 临界区 */ unlock_in_order(); return NULL; }

int main(void){ pthread_t a,b; pthread_create(&a,NULL,A,NULL); pthread_create(&b,NULL,B,NULL); pthread_join(a,NULL); pthread_join(b,NULL); }

验证:

valgrind --tool=drd -q ./fixed_order   # 应无锁序/死锁告警

5.2 trylock + 退避重试(难统一顺序时)

拿不到第二把锁就放弃第一把,退避片刻后重试,打破“占有且等待”。

// trylock_backoff.c
// gcc -O0 -g trylock_backoff.c -pthread -o trylock_backoff
#include <pthread.h>
#include <unistd.h>

static pthread_mutex_t m1 = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t m2 = PTHREAD_MUTEX_INITIALIZER;

static void lock_both_with_retry() {
    for (;;) {
        pthread_mutex_lock(&m1);
        if (pthread_mutex_trylock(&m2) == 0) return;  // 成功
        pthread_mutex_unlock(&m1);                     // 释放并退避
        usleep(1000);                                  // 简单退避策略
    }
}
static inline void unlock_both(){ pthread_mutex_unlock(&m2); pthread_mutex_unlock(&m1); }

void* A(void* arg){ lock_both_with_retry(); /* 临界区 */ unlock_both(); return NULL; }
void* B(void* arg){ lock_both_with_retry(); /* 临界区 */ unlock_both(); return NULL; }

int main(void){ pthread_t a,b; pthread_create(&a,NULL,A,NULL); pthread_create(&b,NULL,B,NULL); pthread_join(a,NULL); pthread_join(b,NULL); }

验证:

valgrind --tool=drd -q ./trylock_backoff  # 不应再卡死;如仍有告警,检查退避/释放是否到位

6. DRD 常用参数与读报技巧

  • -q:安静模式,便于聚焦错误摘要。

  • --first-race-only=yes:仅显示首个竞态,减少噪音(调试早期可开)。

  • --exclusive-threshold=<n>:降噪(过滤共享读写的某些模式)。

  • 读报三看:

    1. 地址是否映射到你的共享对象(如 counter、结构体字段)。
    2. 调用栈是否位于临界区边界(加锁前/后)。
    3. 上下文数量/错误总数(冲突密度随压力测试陡增)。

7. FAQ(高频坑)

  • 为什么我这次 0 错误、下次一堆错误?
    并发是非确定性的;请多跑几次、加压(更多循环/线程)、或用 barrier 强制复现。
  • 死锁与饥饿的区别?
    死锁=各方互等永不前进;饥饿=某线程长期抢不到资源但系统仍在运行。
  • volatile 能避免竞态/死锁吗?
    不能。volatile 不提供互斥/顺序;请使用互斥量/原子操作。

8. 一页清单(拿去即用)

  1. 编译:-O0 -g(行号准确)。

  2. 先 Helgrind:扫 data race、条件变量误用、线程退出仍持锁。

  3. 再 DRD:复现实死锁/锁序反转;观察错误数量变化(冲突密度)。

  4. 修复优先级:

    • 统一加锁顺序 > trylock+退避 > 设计简化(减少锁层数,缩短临界区)。
  5. CI 体检:把 helgrind + drd 都纳入并发回归(压力测试 + 报错即失败)。


9. 结语

  • DRD 在死锁与锁序问题上反馈直观、代价较低,适合日常巡检与教学演示;
  • 与 Helgrind 搭配,能在真实项目中建立可落地的并发质量保障。


📖 针对Valgrind 并发调试 :同步B站的视频课程讲解



【linux】Valgrind工具集详解(十三):DRD(线程错误检测器) 用于检测多线程C和C ++程序中的错误 阅读详情

相关推荐

c++ 内存泄漏、死锁之valgrind定位问题

本文就是抛砖引玉,希望你能够更好的使用valgrind,它能够帮你解决很多问题。

weixin_44834554的博客 1301

互斥量、读写锁长占时分析的利器——valgrind的DRD

在进行多线程编程时,我们可能会存在同时操作(读、写)同一份内存的可能性。为了保证数据的正确性,我们往往会使用互斥量、读写锁等同步方法。(转载请指明出于breaksoftware的csdn博客) 互斥量的用法如下 pthread_mutex_lock(&amp;mutex); // do something pthread_mutex_unlock(...

方亮的专栏 1382

使用Valgrind检查并发问题

Valgrind是一个开源的内存调试、内存泄漏检测和性能分析工具。它可以帮助我们检测并发问题,包括数据竞争和死锁。

夏天的狂风的专栏 512

Valgrind 并发调试 :DRD 实战——死锁与锁序一网打尽

本文摘要: 《Valgrind并发调试·Part 2》深入解析DRD工具检测死锁与锁序问题的实战方法。通过最小可复现代码示例,演示两线程因相反加锁顺序导致的偶发死锁现象,并利用同步栅栏强制暴露问题。DRD能有效统计锁冲突密度,帮助定位临界区问题。文章提出两种标准修复方案:统一加锁顺序(首选)和trylock+退避重试机制,并给出验证命令。该教程来自嵌入式专家Jerry,配套书籍和B站视频可供拓展学习。(149字) 关键词: Valgrind、DRD、死锁检测、锁顺序反转、嵌入式调试、并发编程、线程安全

Interview_TC的博客 1384

valgrind 分析死锁

【1】里面的1 表示owner,从上面的打印可以看到mutex 2和 3和owner形成了相互等待的过程,所以造成了死锁。运行后死锁后ctrl+c发送退出信号退出进程。2,代码pthread.c。

陌上花开缓缓归以的博客 334

股票交易接口TDX量化下单接口

对于需要同时操作多只股票的投资者而言,这一功能可以一次性处理多个交易指令,大大提高了交易的效率。通过回测,投资者可以发现策略中的不足之处,进而进行优化和调整,提高策略的可靠性。根据通达信提供的安装指南,下载相应的安装程序并进行安装。这包括设置交易账户的相关信息,如账户名、密码等,以及连接到正确的交易服务器。不同的情况可能会有不同的标准,有的可能要求账户使用达到一定的时长,有的可能对账户内的资产金额有最低要求。可以通过接口提供的监控功能或者第三方工具,实时查看交易状态、交易数据等信息,确保交易按照预期进行。

2401_88470113的博客 1289

死锁问题分析的利器——valgrind的DRD和Helgrind

在《DllMain中不当操作导致死锁问题的分析--死锁介绍》一文中,我们介绍了死锁产生的原因。一般来说,如果我们对线程同步技术掌握不牢,或者同步方案混乱,极容易导致死锁。本文我们将介绍如何使用valgrind排查死锁问题。(转载请指明出于breaksoftware的csdn博客) 构造一个场景 #include &lt;pthread.h&gt; pthre...

方亮的专栏 5982

Python+QMT量化交易实战:从零搭建你的第一个极速策略(附完整代码)

本文提供了一份详细的Python+QMT量化交易实战指南,手把手教你从零搭建一个可回测、可实盘的极速策略。内容涵盖环境配置、QMT核心机制解析、双均线策略的完整代码实现、回测优化方法以及实盘部署要点,帮助量化交易新手快速入门并掌握核心开发流程。

b9c0d的博客 1013

内存检查工具valgrind介绍、安装与使用

一、valgrind介绍 1、valgrind概述 Valgrind是一套Linux下,开放源代码的仿真调试工具的集合。Valgrind由内核(core)以及基于内核的其他调试工具组成。内核类似于一个框架(framework),它模拟了一个CPU环境,并提供服务给其他工具;而其他工具则类似于插件 (plug-in),利用内核提供的服务完成各种特定的内存调试任务。Valgrind的体系结构如下...

mijichui2153的博客 3万+

windows10下Boost的安装与初试

https://blog.youkuaiyun.com/s_lisheng/article/details/72871218 https://blog.youkuaiyun.com/yockie/article/details/8856190?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-12.vipsorttest&depth_1-utm_source=dis

焦露的博客 6395

Win10+Anaconda环境下PaddleOCR保姆级安装指南(附常见报错解决方案)

本文提供Win10+Anaconda环境下PaddleOCR的保姆级安装指南,涵盖从环境配置到模型部署的全流程,特别针对常见报错提供解决方案。通过详细的步骤说明和实用技巧,帮助开发者快速完成PaddleOCR安装与测试,避免常见陷阱,提升OCR开发效率。

weixin_29096377的博客 375

DRD:线程错误检测器

目录 8.1。概观 8.1.1。多线程编程范例8.1.2。POSIX线程编程模型8.1.3。多线程编程问题8.1.4。数据竞赛检测 8.2。使用DRD 8.2.1。DRD命令行选项8.2.2。检测到的错误:数据竞赛8.2.3。检测到的错误:锁定争用8.2.4。检测到的错误:滥用POSIX线程API8.2.5。客户端请求8.2.6。调试C ++ 11程序8.2.7。调试GNOME程序8.2.

swartz_lubel的专栏 1434

海康威视iVMS-4200在银河麒麟系统部署全攻略与疑难排解

在信创国产化进程中,软件兼容性是关键挑战之一。以安防监控领域广泛使用的海康威视iVMS-4200客户端为例,其从Windows环境迁移到银河麒麟等国产操作系统时,面临架构适配、依赖库匹配等核心兼容性问题。这涉及到跨平台软件部署的通用原理:不同CPU架构(如ARM的飞腾、x86的兆芯)和操作系统底层API的差异,要求软件必须进行针对性编译和优化。解决这类问题的技术价值在于保障专业软件在国产化环境中的稳定运行,是信创产业落地的重要环节。具体到应用场景,如在基于银河麒麟V10的安防监控项目中,通过获取对应架构的.

weixin_33423874的博客 1443

C++线程异常检测工具(Valrind之helgrind和DRD)

从日志中,也可以找到相应的锁的签名,看到其对应的关系和顺序。在平常工作生活中,可以借助检测结果,可以快速的发现,可能存在死锁风险问题。因此,通过查看每个锁占用的时间,根据自己的业务逻辑需要进行判定是否可以减小锁的范围,减小独占时间,提高并发性。只能检测某一个锁占用的时间,不能统计多处、多个锁的占用时间。一般程序退出时,都会解锁,但是在程序退出时,仍然持有锁,说明此处发生死锁了。此工具可支持检测:多线程接口使用问题、竞态问题、锁范围问题。首先将应用程序,编译多线程场景下的可执行程序,需要链接。

youngzhiyong的博客 1093

华为2024机考真题集合

2.中位数是指把一组数据从小到大排列,最中间的那个数,如果这组数据的个数是奇数,那最中间那个就是中位数,如果这组数据的个数为偶数,那就把中间的两个数之和除以 2,所得的结果就是中位数。在实际考试中会随机抽三道题目进行考察,其中两道100分卷题和一道200分卷题,总共400分。幼儿园组织活动,老师布置了一个任务:每个小朋友去了解与自己同一个小区的小朋友还有几个。3.查找整型数组中元素的众数并组成一个新的数组,求新数组的中位数。1.众数是指一组数据中出现次数量多的那个数,众数可以是多个。

qq_25360769的博客 5093

【数据集】中国各类水文专业常用数据集合集

对中国各类水文专业常用数据集进行整理并总结

WW、forever的博客 9302

valgrind 的 Mac 安装方法

从  http://www.valgrind.org  下载最新的3.8.1版本 1. 解压 tar jxvf valgrind-3.8.1.tar.bz2 2. 到解压目录 cd valgrind-3.8.1 3. 配置 ../configure 4. 这一步是关键,否则编译不过去  sudo xcode-select -switch /Applica

荣 的专栏 2776

Docker Desktop 从 C 盘迁移到 D 盘的正确方法

本文介绍了三种将Docker数据目录从C盘迁移到D盘的方案。方案1针对WSL2用户,通过导出/导入虚拟磁盘实现迁移;方案2采用软链接方式,适合所有Docker模式;方案3则建议重装Docker到D盘。其中WSL2迁移方案(方案1)最为稳定可靠,推荐使用。文章详细说明了各方案的操作步骤及注意事项,并提供了迁移后常见问题的解决方法,帮助用户彻底解决Docker占用C盘空间的问题。

sg_knight的专栏 2178

linux多线程互斥锁程序卡死排查 /valgrind

发现程序退出了有锁没有被释放 #通过thread 6找到了是324行创建的线程函数里面锁的释放有问题。我借助工具是valgrind,能够用来检查内存泄漏、死锁和其他线程问题。gcc编译程序用 -g 选项来编译程序,以便在可执行文件中包含调试信息。可以看到程序运行时候每个函数堆栈分配了多少个,还存在多少个。这个是每个线程运行完了之后,有多少个锁还没有释放。运行程序发现程序卡住。

asitMJ的博客 1170

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

关于项目中的内存问题、死锁问题如何定位?——Valgrind

用调试工具调试这个core 文件也能定位到代码行, 但是从对应行的代码看, 可能这行代码本身并没有什么问题, 它只是一个“被陷害者”。3、使用valgrind分析以后发现有一个类内成员变量出现了越界。一切对malloc()/free()/new/delete的调用都会被捕获。在实际处理器的基础上仿真一个虚拟处理器,使应用程序运行于这个虚拟处理器之上,从而进行监视分析。内存泄漏、越界可以用valgrind工具包的Memcheck工具进行定位。1、当时的core文件并没有提供相关的信息。

weixin_44322824的博客 477

ssm框架搭建

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

内存泄漏、死锁:定位排查工具+解决方案(C/C++ 实战指南)

本文针对C/C++开发中的内存泄漏和死锁问题,提供了从定位到解决的全流程实战指南。对于内存泄漏,介绍了Valgrind、AddressSanitizer等工具的用法,分析了常见泄漏场景及智能指针、RAII等解决方案。对于死锁问题,详细讲解了pstack+gdb、ThreadSanitizer等诊断工具的使用方法,并给出预防死锁的编码规范。文章覆盖Linux/Windows平台,包含基础判断方法、工具使用示例和典型问题解决方案,适用于从开发调试到生产环境的各种场景。

A Charmer 的博客 2143

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

死锁(涵盖产生原因,解决方法,GDB,Valgrind死锁调试)

破坏互斥条件:使用无锁数据结构,既然多线程通信需要频繁使用锁,如果使用链式双队列的形式通信,有点类似于发邮件,每个线程独有一个消息队列,通信只需要往特定的队列放消息就行,就不需要锁了。总的来说,解决和预防死锁的方法很多,但最基础的还是按照顺序申请锁,锁的申请和释放成对出现,那么出现这种问题,但代码很多,看起来不方便,这就需要一些调试方法了。破坏循环等待条件:对锁进行全局排序,要求线程按固定顺序申请锁,比如(如锁1、锁2、锁3),如果线程已持有锁2,则不能再申请锁1,只能申请锁3。

2301_80371156的博客 560

valgrind基本功能介绍、基础使用方法说明

Valgrind是一套Linux下,开放源代码(GPL V2)的仿真调试工具的集合。Valgrind由内核(core)以及基于内核的其他调试工具组成。内核类似于一个框架(framework),它模拟了一个CPU环境,并提供服务给其他工具;而其他工具则类似于插件 (plug-in),利用内核提供的服务完成各种特定的内存调试任务。这是valgrind应用最广泛的工具,一个重量级的内存检查器,能够发现开发中绝大多数内存错误使用情况,比如:使用未初始化的内存,使用已经释放了的内存,内存访问越界等。

嵌入式与Linux那些事的博客 7177

C++ 中排查内存泄漏和死锁的详细步骤

通过上述工具和手动检查的方法,可以有效地排查 C++ 程序中的内存泄漏和死锁问题。在实际开发中,应该养成良好的编程习惯,合理使用内存管理工具和并发控制机制,以避免这些问题的发生。同时,使用工具时要仔细分析输出结果,找出问题的根源并进行修复。

qq_40844444的博客 1705

valgrind内存泄露检测工具

Valgrind是一款用于内存调试、内存泄漏检测以及性能分析、检测线程错误的软件开发工具。Valgrind 是运行在Linux 上的多用途代码剖析和内存调试软件。主要包括Memcheck、Callgrind、Cachegrind 等工具,每个工具都能完成一项任务调试、检测或分析。可以检测内存泄露、线程违例和Cache 的使用等。Valgrind 基于仿真方式对程序进行调试,它先于应用程序获取实际处理器的控制权,并在实际处理器的基础上仿真一个虚拟处理器,并使应用程序运行于这个虚拟处理器之上,从而对应用程序的运

qq_37892781的博客 2511

插桩valgrind_(转)valgrind工具介绍

一、简介Valgrind是一个二进制插桩框架,可以用来制作二进制分析工具。利用Valgrind可以检测二进制程序的内存和线程漏洞。Valgrind框架目前包含以下几种工具:Memcheckis a memory error detector. It helps you make your programs, particularly those written in C and C++, mor...

weixin_33622128的博客 417
上一篇: Valgrind 并发调试 :DRD 实战——死锁与锁序一网打尽
下一篇: Valgrind 并发调试 :用 Helgrind 抓住线程里的“看不见的错”
嵌入式孙老师
博客等级 码龄7年 5687粉丝 901原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值