C语言多线程编程全面精讲:从小白到专家(附示例源码)

C语言多线程编程全面精讲:从小白到专家(附示例源码)

一、多线程编程基础概念

1.1 线程与进程的区别

在开始多线程编程前,我们需要明确线程(Thread)和进程(Process)的关键区别:

  • 进程:操作系统资源分配的基本单位,拥有独立的地址空间
  • 线程:CPU调度的基本单位,共享进程的地址空间
/*
 * 进程 vs 线程对比:
 * 
 * | 特性          | 进程                   | 线程                   |
 * |---------------|------------------------|------------------------|
 * | 内存空间      | 独立                   | 共享                   |
 * | 创建开销      | 大                     | 小                     |
 * | 通信方式      | 管道、消息队列、共享内存 | 全局变量、互斥锁等      |
 * | 上下文切换    | 慢                     | 快                     |
 * | 安全性        | 高(隔离)             | 低(共享内存易冲突)    |
 */

1.2 POSIX线程标准

C语言本身不包含多线程支持,我们使用POSIX线程(Pthreads)标准:

#include <pthread.h>  // 必须包含的头文件

// 编译时需要链接pthread库
// gcc program.c -o program -lpthread

二、线程创建与管理

2.1 创建线程:pthread_create()

函数原型

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                   void *(*start_routine) (void *), void *arg);

参数解析

  • thread:指向线程标识符的指针
  • attr:设置线程属性,NULL表示默认
  • start_routine:线程函数入口地址
  • arg:传递给线程函数的参数

基础示例

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

// 线程函数
void* print_message(void *msg) {
    char *message = (char *)msg;
    for (int i = 0; i < 5; i++) {
        printf("%s: %d\n", message, i);
        sleep(1);  // 模拟耗时操作
    }
    return NULL;
}

int main() {
    pthread_t thread1, thread2;
    char *msg1 = "Thread 1";
    char *msg2 = "Thread 2";
    
    // 创建两个线程
    pthread_create(&thread1, NULL, print_message, (void *)msg1);
    pthread_create(&thread2, NULL, print_message, (void *)msg2);
    
    // 等待线程结束
    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);
    
    printf("All threads completed!\n");
    return 0;
}

2.2 线程终止

线程终止的三种方式:

  1. 从线程函数return
  2. 调用pthread_exit()
  3. 被其他线程取消(pthread_cancel)
#include <stdio.h>
#include <pthread.h>

void* thread_func(void *arg) {
    int id = *(int *)arg;
    
    if (id == 1) {
        // 方式1:通过return退出
        printf("Thread %d exiting by return\n", id);
        return (void *)1;
    } else {
        // 方式2:通过pthread_exit退出
        printf("Thread %d exiting by pthread_exit\n", id);
        pthread_exit((void *)2);
    }
}

int main() {
    pthread_t t1, t2;
    int id1 = 1, id2 = 2;
    
    pthread_create(&t1, NULL, thread_func, &id1);
    pthread_create(&t2, NULL, thread_func, &id2);
    
    void *retval;
    pthread_join(t1, &retval);
    printf("Thread 1 returned %ld\n", (long)retval);
    
    pthread_join(t2, &retval);
    printf("Thread 2 returned %ld\n", (long)retval);
    
    return 0;
}

三、线程同步机制

3.1 互斥锁(Mutex)

基本用法

#include <stdio.h>
#include <pthread.h>

int shared_counter = 0;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

void* increment_counter(void *arg) {
    for (int i = 0; i < 100000; i++) {
        pthread_mutex_lock(&mutex);  // 加锁
        shared_counter++;
        pthread_mutex_unlock(&mutex);  // 解锁
    }
    return NULL;
}

int main() {
    pthread_t t1, t2;
    
    pthread_create(&t1, NULL, increment_counter, NULL);
    pthread_create(&t2, NULL, increment_counter, NULL);
    
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    
    printf("Final counter value: %d (expected 200000)\n", shared_counter);
    return 0;
}

高级特性 - 尝试加锁

// 在原有代码中修改increment_counter函数
void* increment_counter(void *arg) {
    for (int i = 0; i < 100000; ) {
        if (pthread_mutex_trylock(&mutex) == 0) {  // 尝试加锁
            shared_counter++;
            pthread_mutex_unlock(&mutex);
            i++;
        } else {
            // 锁被占用时的处理
            usleep(100);  // 短暂休眠避免忙等待
        }
    }
    return NULL;
}

3.2 条件变量(Condition Variables)

条件变量用于线程间的通知机制:

#include <stdio.h>
#include <pthread.h>

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int ready = 0;

void* producer(void *arg) {
    printf("Producer starting...\n");
    sleep(2);  // 模拟生产耗时
    
    pthread_mutex_lock(&mutex);
    ready = 1;
    printf("Producer: data is ready!\n");
    pthread_cond_signal(&cond);  // 通知消费者
    pthread_mutex_unlock(&mutex);
    
    return NULL;
}

void* consumer(void *arg) {
    printf("Consumer waiting...\n");
    
    pthread_mutex_lock(&mutex);
    while (!ready) {  // 必须用while循环检查条件
        pthread_cond_wait(&cond, &mutex);  // 自动释放锁并等待
    }
    printf("Consumer: processing data\n");
    pthread_mutex_unlock(&mutex);
    
    return NULL;
}

int main() {
    pthread_t prod, cons;
    
    pthread_create(&prod, NULL, producer, NULL);
    pthread_create(&cons, NULL, consumer, NULL);
    
    pthread_join(prod, NULL);
    pthread_join(cons, NULL);
    
    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&cond);
    
    return 0;
}

3.3 读写锁(Read-Write Locks)

适用于读多写少的场景:

#include <stdio.h>
#include <pthread.h>

pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
int shared_data = 0;

void* reader(void *arg) {
    int id = *(int *)arg;
    
    pthread_rwlock_rdlock(&rwlock);
    printf("Reader %d: read shared_data = %d\n", id, shared_data);
    usleep(100000);  // 模拟读取耗时
    pthread_rwlock_unlock(&rwlock);
    
    return NULL;
}

void* writer(void *arg) {
    int id = *(int *)arg;
    
    pthread_rwlock_wrlock(&rwlock);
    shared_data++;
    printf("Writer %d: updated shared_data to %d\n", id, shared_data);
    usleep(200000);  // 模拟写入耗时
    pthread_rwlock_unlock(&rwlock);
    
    return NULL;
}

int main() {
    pthread_t readers[5], writers[2];
    int ids[5] = {1, 2, 3, 4, 5};
    
    // 创建3个读者
    for (int i = 0; i < 3; i++) {
        pthread_create(&readers[i], NULL, reader, &ids[i]);
    }
    
    // 创建2个写者
    for (int i = 0; i < 2; i++) {
        pthread_create(&writers[i], NULL, writer, &ids[i]);
    }
    
    // 再创建2个读者
    for (int i = 3; i < 5; i++) {
        pthread_create(&readers[i], NULL, reader, &ids[i]);
    }
    
    // 等待所有线程完成
    for (int i = 0; i < 5; i++) {
        pthread_join(readers[i], NULL);
    }
    for (int i = 0; i < 2; i++) {
        pthread_join(writers[i], NULL);
    }
    
    pthread_rwlock_destroy(&rwlock);
    return 0;
}

四、线程属性与高级控制

4.1 设置线程属性

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

void* thread_func(void *arg) {
    printf("This is a detached thread!\n");
    sleep(2);
    printf("Detached thread exiting\n");
    return NULL;
}

int main() {
    pthread_attr_t attr;
    pthread_t thread;
    
    // 初始化线程属性
    pthread_attr_init(&attr);
    
    // 设置为分离状态(不需要pthread_join)
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
    
    // 设置栈大小(1MB)
    size_t stack_size = 1024 * 1024;
    pthread_attr_setstacksize(&attr, stack_size);
    
    pthread_create(&thread, &attr, thread_func, NULL);
    
    // 销毁属性对象
    pthread_attr_destroy(&attr);
    
    // 主线程等待一段时间让分离线程完成
    sleep(3);
    printf("Main thread exiting\n");
    
    return 0;
}

4.2 线程局部存储(Thread-Local Storage)

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

// 定义线程局部变量
__thread int thread_local_var = 0;

void* thread_func(void *arg) {
    int id = *(int *)arg;
    thread_local_var = id * 10;
    
    printf("Thread %d: initial value = %d\n", id, thread_local_var);
    
    for (int i = 0; i < 3; i++) {
        thread_local_var++;
        printf("Thread %d: value = %d\n", id, thread_local_var);
        sleep(1);
    }
    
    return NULL;
}

int main() {
    pthread_t t1, t2;
    int id1 = 1, id2 = 2;
    
    pthread_create(&t1, NULL, thread_func, &id1);
    pthread_create(&t2, NULL, thread_func, &id2);
    
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    
    return 0;
}

五、线程安全与死锁预防

5.1 线程安全函数设计

非线程安全示例

#include <stdio.h>
#include <pthread.h>

// 非线程安全的伪随机数生成器
unsigned int next = 1;

/* 非线程安全的rand实现 */
int my_rand(void) {
    next = next * 1103515245 + 12345;
    return (unsigned int)(next / 65536) % 32768;
}

void* thread_func(void *arg) {
    for (int i = 0; i < 5; i++) {
        printf("Thread %ld: %d\n", (long)arg, my_rand());
    }
    return NULL;
}

int main() {
    pthread_t t1, t2;
    
    pthread_create(&t1, NULL, thread_func, (void *)1);
    pthread_create(&t2, NULL, thread_func, (void *)2);
    
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    
    return 0;
}

线程安全改进版

#include <stdio.h>
#include <pthread.h>

// 线程安全的伪随机数生成器
unsigned int next = 1;
pthread_mutex_t rand_mutex = PTHREAD_MUTEX_INITIALIZER;

/* 线程安全的rand实现 */
int my_rand_safe(void) {
    pthread_mutex_lock(&rand_mutex);
    next = next * 1103515245 + 12345;
    int result = (unsigned int)(next / 65536) % 32768;
    pthread_mutex_unlock(&rand_mutex);
    return result;
}

void* thread_func(void *arg) {
    for (int i = 0; i < 5; i++) {
        printf("Thread %ld: %d\n", (long)arg, my_rand_safe());
    }
    return NULL;
}

int main() {
    pthread_t t1, t2;
    
    pthread_create(&t1, NULL, thread_func, (void *)1);
    pthread_create(&t2, NULL, thread_func, (void *)2);
    
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    
    pthread_mutex_destroy(&rand_mutex);
    return 0;
}

5.2 死锁示例与预防

死锁示例

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;

void* thread1_func(void *arg) {
    pthread_mutex_lock(&mutex1);
    printf("Thread 1 acquired mutex1\n");
    sleep(1);  // 故意sleep让死锁更容易出现
    
    pthread_mutex_lock(&mutex2);  // 这里会阻塞,因为thread2持有mutex2
    printf("Thread 1 acquired mutex2\n");
    
    // 临界区代码...
    
    pthread_mutex_unlock(&mutex2);
    pthread_mutex_unlock(&mutex1);
    return NULL;
}

void* thread2_func(void *arg) {
    pthread_mutex_lock(&mutex2);
    printf("Thread 2 acquired mutex2\n");
    sleep(1);  // 故意sleep让死锁更容易出现
    
    pthread_mutex_lock(&mutex1);  // 这里会阻塞,因为thread1持有mutex1
    printf("Thread 2 acquired mutex1\n");
    
    // 临界区代码...
    
    pthread_mutex_unlock(&mutex1);
    pthread_mutex_unlock(&mutex2);
    return NULL;
}

int main() {
    pthread_t t1, t2;
    
    pthread_create(&t1, NULL, thread1_func, NULL);
    pthread_create(&t2, NULL, thread2_func, NULL);
    
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    
    printf("This line will never be reached due to deadlock!\n");
    return 0;
}

死锁预防方案

  1. 固定加锁顺序
// 所有线程都按照mutex1->mutex2的顺序加锁
void* thread_safe_func(void *arg) {
    pthread_mutex_lock(&mutex1);
    pthread_mutex_lock(&mutex2);
    
    // 临界区代码...
    
    pthread_mutex_unlock(&mutex2);
    pthread_mutex_unlock(&mutex1);
    return NULL;
}
  1. 使用pthread_mutex_trylock
void* thread_trylock_func(void *arg) {
    while (1) {
        if (pthread_mutex_trylock(&mutex1) == 0) {
            if (pthread_mutex_trylock(&mutex2) == 0) {
                // 成功获取两个锁
                printf("Thread acquired both locks\n");
                
                // 临界区代码...
                
                pthread_mutex_unlock(&mutex2);
                pthread_mutex_unlock(&mutex1);
                break;
            }
            // 获取mutex2失败,释放mutex1避免死锁
            pthread_mutex_unlock(&mutex1);
        }
        usleep(100000);  // 避免忙等待
    }
    return NULL;
}

六、线程池实现

一个简单的线程池实现:

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>

#define THREAD_POOL_SIZE 4
#define TASK_QUEUE_SIZE 256

typedef struct {
    void (*function)(void *);
    void *arg;
} Task;

typedef struct {
    Task task_queue[TASK_QUEUE_SIZE];
    int queue_front;
    int queue_rear;
    int queue_count;
    
    pthread_mutex_t lock;
    pthread_cond_t notify;
    
    pthread_t workers[THREAD_POOL_SIZE];
    int shutdown;
} ThreadPool;

void* worker_thread(void *arg) {
    ThreadPool *pool = (ThreadPool *)arg;
    
    while (1) {
        pthread_mutex_lock(&pool->lock);
        
        // 等待任务或关闭信号
        while (pool->queue_count == 0 && !pool->shutdown) {
            pthread_cond_wait(&pool->notify, &pool->lock);
        }
        
        // 收到关闭信号且无任务可处理时退出
        if (pool->shutdown && pool->queue_count == 0) {
            pthread_mutex_unlock(&pool->lock);
            pthread_exit(NULL);
        }
        
        // 取出任务
        Task task = pool->task_queue[pool->queue_front];
        pool->queue_front = (pool->queue_front + 1) % TASK_QUEUE_SIZE;
        pool->queue_count--;
        
        pthread_mutex_unlock(&pool->lock);
        
        // 执行任务
        (task.function)(task.arg);
    }
    
    return NULL;
}

ThreadPool* thread_pool_create() {
    ThreadPool *pool = malloc(sizeof(ThreadPool));
    if (!pool) return NULL;
    
    pool->queue_front = 0;
    pool->queue_rear = 0;
    pool->queue_count = 0;
    pool->shutdown = 0;
    
    pthread_mutex_init(&pool->lock, NULL);
    pthread_cond_init(&pool->notify, NULL);
    
    for (int i = 0; i < THREAD_POOL_SIZE; i++) {
        pthread_create(&pool->workers[i], NULL, worker_thread, pool);
    }
    
    return pool;
}

int thread_pool_add_task(ThreadPool *pool, void (*function)(void *), void *arg) {
    pthread_mutex_lock(&pool->lock);
    
    if (pool->queue_count == TASK_QUEUE_SIZE) {
        pthread_mutex_unlock(&pool->lock);
        return -1;  // 队列已满
    }
    
    if (pool->shutdown) {
        pthread_mutex_unlock(&pool->lock);
        return -2;  // 线程池已关闭
    }
    
    // 添加任务到队列
    pool->task_queue[pool->queue_rear].function = function;
    pool->task_queue[pool->queue_rear].arg = arg;
    pool->queue_rear = (pool->queue_rear + 1) % TASK_QUEUE_SIZE;
    pool->queue_count++;
    
    // 通知一个等待的线程
    pthread_cond_signal(&pool->notify);
    pthread_mutex_unlock(&pool->lock);
    
    return 0;
}

void thread_pool_destroy(ThreadPool *pool) {
    if (!pool) return;
    
    pthread_mutex_lock(&pool->lock);
    pool->shutdown = 1;
    pthread_mutex_unlock(&pool->lock);
    
    // 唤醒所有线程
    pthread_cond_broadcast(&pool->notify);
    
    // 等待所有线程退出
    for (int i = 0; i < THREAD_POOL_SIZE; i++) {
        pthread_join(pool->workers[i], NULL);
    }
    
    pthread_mutex_destroy(&pool->lock);
    pthread_cond_destroy(&pool->notify);
    
    free(pool);
}

// 测试任务函数
void print_task(void *arg) {
    int id = *(int *)arg;
    printf("Task %d processed by thread %lu\n", id, pthread_self());
    free(arg);  // 释放动态分配的内存
    usleep(100000);  // 模拟任务处理时间
}

int main() {
    ThreadPool *pool = thread_pool_create();
    
    // 添加20个任务
    for (int i = 0; i < 20; i++) {
        int *arg = malloc(sizeof(int));
        *arg = i;
        thread_pool_add_task(pool, print_task, arg);
    }
    
    // 等待所有任务完成
    sleep(3);
    
    thread_pool_destroy(pool);
    return 0;
}

七、性能考量与最佳实践

7.1 多线程性能优化建议

  1. 避免过度线程化:线程数通常不应超过CPU核心数的2-4倍
  2. 减少锁竞争
    • 使用读写锁替代互斥锁(读多写少场景)
    • 采用细粒度锁(为不同数据使用不同锁)
    • 使用无锁数据结构(如原子操作)
  3. 避免虚假共享:确保频繁访问的独立数据不在同一缓存行
#include <stdio.h>
#include <pthread.h>
#include <time.h>

#define ARRAY_SIZE 1000000
#define THREAD_COUNT 4

// 有虚假共享的结构体
struct BadStruct {
    int a;
    int b;
    int c;
    int d;
};

// 无虚假共享的结构体(使用填充)
struct GoodStruct {
    int a;
    char padding1[60];  // 假设缓存行大小为64字节
    int b;
    char padding2[60];
    int c;
    char padding3[60];
    int d;
};

void* worker_bad(void *arg) {
    struct BadStruct *data = (struct BadStruct *)arg;
    for (int i = 0; i < ARRAY_SIZE; i++) {
        data->a++;  // 四个线程访问同一缓存行的不同部分
    }
    return NULL;
}

void* worker_good(void *arg) {
    struct GoodStruct *data = (struct GoodStruct *)arg;
    for (int i = 0; i < ARRAY_SIZE; i++) {
        data->a++;  // 每个线程访问独立的缓存行
    }
    return NULL;
}

int main() {
    pthread_t threads[THREAD_COUNT];
    struct timespec start, end;
    
    // 测试有虚假共享的情况
    struct BadStruct bad_data = {0};
    clock_gettime(CLOCK_MONOTONIC, &start);
    for (int i = 0; i < THREAD_COUNT; i++) {
        pthread_create(&threads[i], NULL, worker_bad, &bad_data);
    }
    for (int i = 0; i < THREAD_COUNT; i++) {
        pthread_join(threads[i], NULL);
    }
    clock_gettime(CLOCK_MONOTONIC, &end);
    double bad_time = (end.tv_sec - start.tv_sec) + (end.tv_nsec - start.tv_nsec) / 1e9;
    
    // 测试无虚假共享的情况
    struct GoodStruct good_data = {0};
    clock_gettime(CLOCK_MONOTONIC, &start);
    for (int i = 0; i < THREAD_COUNT; i++) {
        pthread_create(&threads[i], NULL, worker_good, &good_data);
    }
    for (int i = 0; i < THREAD_COUNT; i++) {
        pthread_join(threads[i], NULL);
    }
    clock_gettime(CLOCK_MONOTONIC, &end);
    double good_time = (end.tv_sec - start.tv_sec) + (end.tv_nsec - start.tv_nsec) / 1e9;
    
    printf("有虚假共享的运行时间: %.6f秒\n", bad_time);
    printf("无虚假共享的运行时间: %.6f秒\n", good_time);
    
    return 0;
}

7.2 多线程编程最佳实践

  1. 资源管理原则

    • 谁分配谁释放
    • 加锁和解锁在同一个函数中进行
    • 使用RAII模式管理资源(C中可用goto实现类似效果)
  2. 错误处理

    • 检查所有系统调用的返回值
    • 为线程设置取消点
    • 实现线程清理处理程序
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>

// 线程清理处理程序
void cleanup_handler(void *arg) {
    printf("Cleanup handler: releasing mutex\n");
    pthread_mutex_unlock((pthread_mutex_t *)arg);
}

void* thread_func(void *arg) {
    pthread_mutex_t *mutex = (pthread_mutex_t *)arg;
    
    // 注册清理处理程序
    pthread_cleanup_push(cleanup_handler, mutex);
    
    pthread_mutex_lock(mutex);
    printf("Thread acquired mutex\n");
    
    // 模拟工作(可能被取消)
    sleep(2);
    
    printf("Thread releasing mutex\n");
    pthread_mutex_unlock(mutex);
    
    // 注销清理处理程序(非必要,但保持push/pop配对)
    pthread_cleanup_pop(0);
    
    return NULL;
}

int main() {
    pthread_t thread;
    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    
    pthread_create(&thread, NULL, thread_func, &mutex);
    
    // 主线程等待一段时间
    sleep(1);
    
    // 取消子线程
    printf("Main thread cancelling worker thread\n");
    pthread_cancel(thread);
    
    // 等待子线程结束
    pthread_join(thread, NULL);
    
    // 尝试加锁(验证清理处理程序是否工作)
    if (pthread_mutex_trylock(&mutex) == 0) {
        printf("Main thread acquired mutex (cleanup worked)\n");
        pthread_mutex_unlock(&mutex);
    } else {
        printf("Mutex still locked (cleanup failed)\n");
    }
    
    pthread_mutex_destroy(&mutex);
    return 0;
}

八、划重点喽!!!!!

本文全面介绍了C语言多线程编程的各个方面,从基础概念到高级应用,包括:

  1. 线程创建与管理:pthread_create、pthread_join等基本操作
  2. 线程同步机制:互斥锁、条件变量、读写锁的使用
  3. 高级线程控制:线程属性、线程局部存储
  4. 线程安全与死锁:常见问题与解决方案
  5. 线程池实现:提高多线程效率的设计模式
  6. 性能优化:虚假共享、锁竞争等性能问题的处理

多线程编程的核心要点:

  • 正确性优先:确保线程安全,避免竞态条件和死锁
  • 合理设计:根据任务特性选择适当的同步机制
  • 性能考量:平衡线程数量和资源消耗
  • 错误处理:妥善处理线程中的异常情况

但是要真正掌握多线程编程,建议各位大佬们:

  1. 从简单示例开始,逐步构建复杂系统
  2. 使用工具检测问题(如valgrind、helgrind)
  3. 阅读优秀的开源多线程代码(如Redis、Nginx)
  4. 在实践中不断总结经验教训

爱你们吆!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

十一剑的CS_DN博客

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值