Linux C/C++之线程基础

目录

1. 什么是线程

1.1 进程与线程

1.2 线程

1.3 线程的发展史

2. 如何创建,使用线程

2.1 pthread_create函数创建线程

2.2 pthread_create函数的使用

2.3 主线程结束, 分支线程也会随之结束

2.4 同一进程内多个线程共用进程资源

2.5 线程使用进程开辟的动态内存,主线程提前释放内存会造成的问题

2.6 使用地址传递传递数据到线程运行函数

2.7 使用值传递传递数据到线程运行函数

2.8 结构体数据类型的参数传递

3. 线程的结束

3.1 自然结束

3.2 主线程结束, 分支线程随之结束 

3.3 线程自己结束自己

3.4 其它线程结束某个线程

4. 线程的同步

4.1 临界区域与临界数据

4.2 多个线程同时操作临界数据导致的问题

4.3 使用线程同步解决临界数据脏的方式

4.3.1 原子锁(atomic)

4.3.2 自旋锁(spin)

4.3.3 信号量(sem)

4.3.4 读写锁(rwlock)

4.3.5 互斥锁(mutex)

4.3.6 临界变量(cond)


1. 什么是线程

1.1 进程与线程

进程是操作系统资源调度的基本单位

线程是操作系统调度的基本单位

1.2 线程

 线程,被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元

1.3 线程的发展史

2.2版本的内核中, 没有线程, 进程的个数也有限, 4090个, 线程是轻量级的进程, 2.4版本的内核中, 有了线程的概念, 线程的个数可以无限, 协程是更轻量级的线程

2. 如何创建,使用线程

2.1 pthread_create函数创建线程

 

创建并执行线程,执行代码为线程函数,并且和当前主线程并行

2.2 pthread_create函数的使用

//使用pthread_create 创建一个线程
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

void* pFunc(void* arg){
	int m = 1;
	while(1){
		printf("线程 --- m: %d\n",m++);
		sleep(1);
	}
}
int main(){
	int n = 1;
	//创建一个线程
	pthread_t pid;
	pthread_create(&pid,NULL,pFunc,NULL);
	while(1){
		printf("主函数 --- n: %d\n",n++);
		sleep(1);
	}
	return 0;
}

 

2.3 主线程结束, 分支线程也会随之结束

//当主线程结束,分支线程也会随即结束
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

void* pFunc(void* arg){
	int m = 1;
	while(1){
		printf("线程 --- m: %d\n",m++);
		sleep(1);
	}
}
int main(){
	int n = 1;
	//创建一个线程
	pthread_t pid;
	pthread_create(&pid,NULL,pFunc,NULL);
	for(int i = 0; i < 5; i++){
		printf("主函数 --- n: %d\n",n++);
		sleep(1);
	}
	return 0;
}

 

2.4 同一进程内多个线程共用进程资源

//同一进程内的线程共用进程的资源
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

int num = 0;
void* pFunc1(void* arg){
	while(1){
		printf("线程1 --- num: %d\n",num++);
		sleep(1);
	}
}

void* pFunc2(void* arg){	//先于线程一执行
	while(1){
		printf("线程2 --- num: %d\n",num++);
		sleep(1);
	}
}

int main(){
	//创建一个线程
	pthread_t pid1,pid2;
	pthread_create(&pid1,NULL,pFunc1,NULL);
	pthread_create(&pid2,NULL,pFunc2,NULL);
	while(1){
		printf("主函数 --- num: %d\n",num++);
		sleep(1);
	}
	return 0;
}

2.5 线程使用进程开辟的动态内存,主线程提前释放内存会造成的问题

//当使用动态内存申请时,主线程提前将内存释放会造成的问题
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>

void* pFunc(void* arg){
	while(1){
		printf("线程 --- arg: %d\n",*(int*)arg);
		sleep(1);
	}
}
int main(){
	int* n = (int*)malloc(4);
	*n = 666;
	//创建一个线程
	pthread_t pid;
	pthread_create(&pid,NULL,pFunc,n);
	for(int i = 0; i < 5; i++){
		if(i == 2){
			free(n);
			n = NULL;
		}
		printf("主函数 --- n: %d\n",(*n)++);
		sleep(1);
	}
	return 0;
}

2.6 使用地址传递传递数据到线程运行函数

//使用变量的地址传参 &
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

void* pFunc(void* arg){
	while(1){
		printf("线程 --- arg: %d\n",(*(int*)arg)++);
		sleep(1);
	}
}
int main(){
	int n = 666;
	//创建一个线程
	pthread_t pid;
	pthread_create(&pid,NULL,pFunc,&n);
	while(1){
		printf("主函数 --- n: %d\n",n++);
		sleep(1);
	}
	return 0;
}

2.7 使用值传递传递数据到线程运行函数

//使用(void*)变量直接传参
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

void* pFunc(void* arg){
	while(1){
		printf("线程 --- arg: %d\n",(int)arg++);
		sleep(1);
	}
}
int main(){
	int n = 666;
	//创建一个线程
	pthread_t pid;
	pthread_create(&pid,NULL,pFunc,(void*)n);
	while(1){
		printf("主函数 --- n: %d\n",n++);
		sleep(1);
	}
	return 0;
}

2.8 结构体数据类型的参数传递

//结构体类型的参数传递
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>

struct Student{
	char name[20];
	int age;
	double score;
};

void* pFunc(void* arg){
	struct Student* stu = (struct Student*)arg;
	while(1){
		printf("线程===\n");
		printf("name:%s,age:%d,score:%g\n",
			stu->name,stu->age,stu->score);
		sleep(1);
	}
}

int main(){

	struct Student stu = {"张三",18,66.66};
	pthread_t pid;
	pthread_create(&pid,NULL,pFunc,&stu);
	for(int i = 0; i < 5; i++){
		printf("主函数---\n");
		sleep(1);
	}

	return 0;
}

3. 线程的结束

3.1 自然结束

线程运行完自己应该执行的代码块后自然结束

3.2 主线程结束, 分支线程随之结束 

注: 主线程最好等待分支线程结束再结束

pthread_join函数使用

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

void* func(void* arg){
	while(1){
		sleep(2);
		printf("线程-----\n");	
	}
}

int main(){
	pthread_t pid;

	pthread_create(&pid,NULL,func,NULL);

	printf("主线程-----\n");

	return 0;
}

没有pthread_join函数的情况 , 主线程不会等待分支线程结束后再结束

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

void* func(void* arg){
	while(1){
		sleep(2);
		printf("线程-----\n");	
	}
}

int main(){
	pthread_t pid;

	pthread_create(&pid,NULL,func,NULL);

	printf("主线程-----\n");

	pthread_join(pid,NULL);

	return 0;
}

 有pthread_join的情况,主线程会等待分支线程结束后,自己再结束

3.3 线程自己结束自己

进程的结束使用 exit   或    _exit

线程的结束使用 pthread_exit

//线程自己结束自己pthread_exit
//void pthread_exit(void *retval); retval线程结束的返回值
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>

void* func(void* arg){
	int num = 666;
	for(int i = 0;i < 10; i++){
		if(i == 5){
			//pthread_exit((void*)num);
			pthread_exit((void*)"线程退出!");
		}
		printf("线程 >> %d\n",(*(int*)arg)++);
		sleep(1);
	}
	
}

int main(){
	int n = 1;
	void* pthread_res; //接收线程结束的返回值
	pthread_t pid;

	pthread_create(&pid,NULL,func,&n);

	pthread_join(pid,&pthread_res); //等待线程结束并接收返回值

	//printf("pthread_return: %d\n",(int)pthread_res);
	printf("pthread_return: %s\n",(char*)pthread_res);
	printf(" >> %d\n",n);

	return 0;
}

3.4 其它线程结束某个线程

pthread_cancel函数: 仅向线程发送一个结束请求, 至于是否结束看pthread_setcancelstate()函数参数一是忽略(PTHREAD_CANCEL_DISABLE)还是响应(PTHREAD_CANCEL_ENABLE)   

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

int n = 1;
pthread_t pid1;

void* func1(void* arg){
	while(1){
		printf("线程1 >> %d\n",n++);
		//PTHREAD_CANCEL_ENABLE(缺省:响应)
		//PTHREAD_CANCEL_DISABLE(忽略)
		pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,NULL);
		pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED,NULL);
		//PTHREAD_CANCEL_DEFERRED(下个取消点)
		//PTHREAD_CANCEL_ASYNCHRONOUS(异步(随时)取消)

		sleep(1);
	}
}

int main(){

	pthread_create(&pid1,NULL,func1,NULL);
	while(1){
		if(n == 5){
			pthread_cancel(pid1);
			break;
		}
	}

	pthread_join(pid1,NULL);

	printf(">> %d\n",n);

	return 0;
}

 PTHREAD_CANCEL_ENABLE: 

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

int n = 1;
pthread_t pid1;

void* func1(void* arg){
	while(1){
		printf("线程1 >> %d\n",n++);
		//PTHREAD_CANCEL_ENABLE(缺省:响应)
		//PTHREAD_CANCEL_DISABLE(忽略)
		pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL);
		pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED,NULL);
		//PTHREAD_CANCEL_DEFERRED(下个取消点)
		//PTHREAD_CANCEL_ASYNCHRONOUS(异步(随时)取消)
		//pthread_testcancel();  //加一个取消点

		printf("sleep1\n");
		sleep(1);

		printf("sleep2\n");
		sleep(1);

		printf("sleep3\n");
		sleep(1);
	}
}

int main(){

	pthread_create(&pid1,NULL,func1,NULL);
	while(1){
		if(n == 5){
			pthread_cancel(pid1);
			break;
		}
	}

	pthread_join(pid1,NULL);

	printf(">> %d\n",n);

	return 0;
}

 

4. 线程的同步

4.1 临界区域与临界数据

多个线程可以同时 访问 的 区域 称之为 临界区域

多个线程可以同时 操作 的 数据 称之为 临界数据

4.2 多个线程同时操作临界数据导致的问题

//多个线程同时操作临界数据,导致临界数据脏的问题
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>

int n = 0;
void* func1(void* arg){
	for(int i = 0; i < 25000000; i++)
		n++;
}

void* func2(void* arg){
	for(int i = 0; i < 25000000; i++)
		n++;
}

int main(){
	pthread_t p1,p2,p3,p4;

	pthread_create(&p1,NULL,func1,NULL);
	pthread_create(&p2,NULL,func2,NULL);
	pthread_create(&p3,NULL,func1,NULL);
	pthread_create(&p4,NULL,func2,NULL);

	pthread_join(p1,NULL); 	//等待线程结束
	pthread_join(p2,NULL); 	//等待线程结束
	pthread_join(p3,NULL); 	//等待线程结束
	pthread_join(p4,NULL); 	//等待线程结束

	printf("n: %d\n",n);	// <= 100000000

	return 0;
}

4.3 使用线程同步解决临界数据脏的方式

内核态用户态
原子锁(atomic)读写锁(rwlock)
自旋锁(spin)互斥锁(mutex)
信号量(sem)临界变量(cond)

4.3.1 原子锁(atomic)

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

#define atomic_inc(x)  __sync_fetch_and_add(x,1)

void* func1(void* arg){
	for(int i = 0; i < 50000000; i++)
		atomic_inc((int*)arg);
}

void* func2(void* arg){
	for(int i = 0; i < 50000000; i++)
		atomic_inc((int*)arg);
}

int main(){
	pthread_t p1,p2;
	int n = 0;
	
	pthread_create(&p1,NULL,func1,&n);
	pthread_create(&p2,NULL,func2,&n);

	pthread_join(p1,NULL); 	//等待线程结束
	pthread_join(p2,NULL); 	//等待线程结束

	printf("n: %d\n",n);	

	return 0;
}

4.3.2 自旋锁(spin)

自旋锁的建立消耗的资源少, 但是当线程阻塞时, 它会一直循环不断地检查锁是否可用, 因此当线程处于阻塞状态下, 消耗的资源相对较多

  1. 初始化自旋锁         pthread_spin_init        
  2. 获取(上)锁              pthread_spin_lock
  3. 释放(解)锁              pthread_spin_unlock
  4. 销毁锁                    pthread_spin_destroy
//自旋锁(spin)的使用
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>

int n = 0;
//0. 定义自旋锁
pthread_spinlock_t spin;

void* func1(void* arg){
	for(int i = 0; i < 50000000; i++){
		//2. 上锁
		pthread_spin_lock(&spin);
		//操作
		n++;
		//3. 解锁
		pthread_spin_unlock(&spin);
	}
}

void* func2(void* arg){
	for(int i = 0; i < 50000000; i++){
		//2. 上锁
		pthread_spin_lock(&spin);
		//操作
		n++;
		//3. 解锁
		pthread_spin_unlock(&spin);
	}
}

int main(){
	pthread_t p1,p2;

	//1. 初始化自旋锁
	pthread_spin_init(&spin,PTHREAD_PROCESS_PRIVATE);

	pthread_create(&p1,NULL,func1,NULL);
	pthread_create(&p2,NULL,func2,NULL);

	pthread_join(p1,NULL); 	//等待线程结束
	pthread_join(p2,NULL); 	//等待线程结束

	printf("n: %d\n",n);	

	//4. 销毁自旋锁
	pthread_spin_destroy(&spin);

	return 0;
}

4.3.3 信号量(sem)

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

int n = 0;
//0. 定义信号量
sem_t sem;

void* func1(void* arg){
	//信号量值减一
	if(0 == sem_wait(&sem)){
		for(int i = 0; i < 50000000; i++){
			n++;
			//信号量值加一
			sem_post(&sem);
		}
	}	
}

int main(){
	pthread_t p1,p2;

	//1. 初始化信号量
	//参数二 0 当前进程使用   1  多个进程间共享
	sem_init(&sem, 0, 2);

	pthread_create(&p1,NULL,func1,NULL);
	pthread_create(&p2,NULL,func1,NULL);

	pthread_join(p1,NULL); 	//等待线程结束
	pthread_join(p2,NULL); 	//等待线程结束

	printf("n: %d\n",n);

	//销毁信号量
	sem_destroy(&sem);	

	return 0;
}

4.3.4 读写锁(rwlock)

  1. 初始化读写锁        pthread_rwlock_init
  2. 获取锁                   pthread_rwlock_wrlock(写锁)       pthread_rwlock_rdlock(读锁)
  3. 释放锁                   pthread_rwlock_unlock
  4. 销毁锁                   pthread_rwlock_destroy
//读写锁(rwlock)的使用	读读相容
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>

int n = 0;
//0. 定义读写锁变量
pthread_rwlock_t rwlock;

void* func1(void* arg){
	for(int i = 0; i < 50000000; i++){
		//2. 上锁(读锁)
		pthread_rwlock_rdlock(&rwlock);
		//pthread_rwlock_wrlock(&rwlock);
		//操作
		n++;
		//3. 解锁
		pthread_rwlock_unlock(&rwlock);
	}
}

void* func2(void* arg){
	for(int i = 0; i < 50000000; i++){
		//2. 上锁(读锁)
		pthread_rwlock_rdlock(&rwlock);
		//pthread_rwlock_wrlock(&rwlock);
		//操作
		n++;
		//3. 解锁
		pthread_rwlock_unlock(&rwlock);
	}
}

int main(){
	pthread_t p1,p2;

	//1. 初始化读写锁
	pthread_rwlock_init(&rwlock,NULL);

	pthread_create(&p1,NULL,func1,NULL);
	pthread_create(&p2,NULL,func2,NULL);

	pthread_join(p1,NULL); 	//等待线程结束
	pthread_join(p2,NULL); 	//等待线程结束

	printf("n: %d\n",n);	

	//4. 销毁读写锁
	pthread_rwlock_destroy(&rwlock); 

	return 0;
}

 读读相容 

 

void* func1(void* arg){
	for(int i = 0; i < 50000000; i++){
		//2. 上锁(读锁)
		pthread_rwlock_rdlock(&rwlock);
		//操作
		n++;
		//3. 解锁
		pthread_rwlock_unlock(&rwlock);
	}
}

void* func2(void* arg){
	for(int i = 0; i < 50000000; i++){
		//2. 上锁(写锁)
		pthread_rwlock_wrlock(&rwlock);
		//操作
		n++;
		//3. 解锁
		pthread_rwlock_unlock(&rwlock);
	}
}

 读写相斥

void* func1(void* arg){
	for(int i = 0; i < 50000000; i++){
		//2. 上锁(写锁)
		pthread_rwlock_wrlock(&rwlock);
		//操作
		n++;
		//3. 解锁
		pthread_rwlock_unlock(&rwlock);
	}
}

void* func2(void* arg){
	for(int i = 0; i < 50000000; i++){
		//2. 上锁(写锁)
		pthread_rwlock_wrlock(&rwlock);
		//操作
		n++;
		//3. 解锁
		pthread_rwlock_unlock(&rwlock);
	}
}

写写相斥        

4.3.5 互斥锁(mutex)

互斥锁适合频繁操作的情况

若等待时间较长, 突然要解决事务(注意临界数据安全的问题)

互斥锁的建立消耗的资源多, 但是当线程阻塞时, 不会循环不断地检查锁是否可用, 因此当线程处于阻塞状态下不消耗资源

因此自旋锁适用于阻塞时间很短的情况, 互斥锁适用于阻塞时间很长的情况

  1. 初始化互斥锁         pthread_mutex_init        
  2. 获取(上)锁              pthread_mutex_lock
  3. 释放(解)锁              pthread_mutex_unlock
  4. 销毁锁                    pthread_mutex_destroy
//互斥锁(mutex)的使用
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>

int n = 0;
//0. 定义互斥变量
pthread_mutex_t mutex;

void* func1(void* arg){
	for(int i = 0; i < 50000000; i++){
		//2. 上锁
		pthread_mutex_lock(&mutex);
		//操作
		n++;
		//3. 解锁
		pthread_mutex_unlock(&mutex);
	}
}

void* func2(void* arg){
	for(int i = 0; i < 50000000; i++){
		//2. 上锁
		pthread_mutex_lock(&mutex);
		//操作
		n++;
		//3. 解锁
		pthread_mutex_unlock(&mutex);
	}
}

int main(){
	pthread_t p1,p2;

	//1. 初始化互斥量
	pthread_mutex_init(&mutex,NULL);

	pthread_create(&p1,NULL,func1,NULL);
	pthread_create(&p2,NULL,func2,NULL);

	pthread_join(p1,NULL); 	//等待线程结束
	pthread_join(p2,NULL); 	//等待线程结束

	printf("n: %d\n",n);
	//4. 销毁互斥锁
	pthread_mutex_destroy(&mutex);

	return 0;
}

 

4.3.6 临界变量(cond)

 临界变量是互斥锁的升级(因此临界变量一般搭配互斥锁使用)

  1. 初始化互斥锁,临界变量         pthread_mutex_init        pthread_cond_init        
  2. 获取(上)锁, 临界变量等待      pthread_mutex_lock      pthread_cond_wait
  3. 释放(解)锁                              pthread_mutex_unlock
  4. 发信号给临界变量                  pthread_cond_signal        pthread_cond_broadcast
  5. 销毁锁,销毁临界变量             pthread_mutex_destroy   pthread_cond_destroy
//临界(条件)变量(cond)的使用
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>

int n = 0;
//0. 定义互斥变量
pthread_mutex_t mutex;
//0. 定义临界变量
pthread_cond_t cond;

void* func1(void* arg){
	for(int i = 0; i <= 5; i++){
		//2. 上锁,临界变量等待
		pthread_mutex_lock(&mutex);
		pthread_cond_wait(&cond,&mutex);
		//操作
		printf("线程1 ------ %d\n",n++);
		//3. 解锁
		pthread_mutex_unlock(&mutex);
	}
}

void* func2(void* arg){
	for(int i = 0; i <= 5; i++){
		//2. 上锁,临界变量等待
		pthread_mutex_lock(&mutex);
		pthread_cond_wait(&cond,&mutex);
		//操作
		printf("线程2 ====== %d\n",n++);
		//3. 解锁
		pthread_mutex_unlock(&mutex);
	}
}

int main(){
	pthread_t p1,p2,p3,p4;

	//1. 初始化互斥锁和临界变量
	pthread_mutex_init(&mutex,NULL);
	pthread_cond_init(&cond,NULL);

	pthread_create(&p1,NULL,func1,NULL);
	pthread_create(&p2,NULL,func2,NULL);

	//4. 发信号给临界变量
	for(int i = 0; i < 13; i++){
		//解除全部线程的阻塞状态(一次)
		//pthread_cond_broadcast(&cond);
		//解除至少一个线程的阻塞状态(一次)
		pthread_cond_signal(&cond);
		//pthread_cond_signal(&cond);
		sleep(1);
	}
	
	pthread_join(p1,NULL); 	//等待线程结束
	pthread_join(p2,NULL); 	//等待线程结束

	printf("n: %d\n",n);	

	//5. 销毁互斥锁和临界变量
	pthread_mutex_destroy(&mutex);
	pthread_cond_destroy(&cond);

	return 0;
}

  • 13
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Linux C/C++ 后台开发实践》是一本介绍在Linux环境下使用C/C++进行后台开发的实践指南。 该书主要分为以下几个部分: 第一部分是介绍Linux基础知识,包括Linux操作系统的原理、常用命令等。这部分的目的是为读者打下Linux基础,为后面的主题提供必要的背景知识。 第二部分是介绍C/C++语言的基础知识,包括语法、指针、内存管理等。这部分的目的是让读者熟悉C/C++语言的基本特性,为后续的后台开发做好准备。 第三部分是介绍Linux下的网络编程,包括Socket编程、网络通信协议等。这部分的目的是让读者了解网络编程的基本原理并能够使用C/C++进行网络通信。 第四部分是介绍多线程编程和进程间通信,包括线程的创建与同步、进程间的通信机制等。这部分的目的是让读者了解多线程编程和进程间通信的原理,并能够使用C/C++进行相关的开发。 第五部分是介绍常用的后台开发框架和技术,包括数据库访问、消息队列、Web服务等。这部分的目的是让读者了解后台开发中常用的框架和技术,并能够使用C/C++进行相关的开发。 通过阅读《Linux C/C++ 后台开发实践》,读者可以系统地了解并掌握在Linux环境下使用C/C++进行后台开发的基本技能。这本书的内容丰富、深入浅出,适合有一定编程基础的读者阅读,并可以作为后台开发工程师的参考书籍。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

石小浪♪

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

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

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

打赏作者

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

抵扣说明:

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

余额充值