线程和进程

 

一 进程

  1.1进程定义

进程是一个容器,包含程序执行需要的代码、数据、资源等等信息。Windows是多任务操作系统,可以同时执行多个进程。

  1.2.Windows进程的特点

   1)每个进程都有自己的ID号

   2)每个进程都有自己的地址空间,进程之间无法访问对方的地址空间。

   3)每个进程都有自己的安全属性

   4)每个进程当中至少包含一个线程

  1.3.进程的id和句柄

    获取当前进程ID:     DWORD GetCurrentProcessId()

    得到当前进程的句柄: HeANDLE GetCurrentProcss( VOID );

    为了安全,返回一个假的句柄,但可以直接使用假的句柄操作当前线程

  1.4创建进程

   BOOL CreateProcess(

            LPCWSTR lpApplicationName,//进程路径

            LPWSTR lpCommandLine,//命令行参数

            LPSECURITY_ATTRIBUTES lpP,//进程安全属性

            LPSECURITY_ATTRIBUTES Attribu,//线程安全属性

            BOOL bInheritHandles,//进程的句柄继承

            DWORD dwCreationFlags,//创建方式

            LPVOID lpEnvironment,//环境信息

            LPCWSTR lpCurrentDirectory,//当前目录

            LPSTARTUPINFOW lpStartupInfo,//起始信息

            LPPROCESS_INFORMATION lp//进程句柄(传出)

);

  1.5等待进程结束

     WaitForSingleObject( 进程句柄, INFINITE );

INFINITE   : 无限制的等待

 

  1.6打开进程—得到进程句柄

      HANDLE OpenProcess(

        DWORD dwDesiredAccess,//打开权限PROCESS_ALL_ACCESS

        BOOL bInheritHandle,//继承方式

       DWORD dwProcessId//进程id    );

  1.7结束当前进程

     Void ExitProcess(UINT uExitCode退出码);

  1.8结束指定进程

    TerminateProcess(进程句柄,退出码)

二. 线程

 

  2.1 线程定义

windows以线程为单位进行调度程序,一个程序中至少有一个线程

可以有多个线程,用来实现多任务的处理

  2.2线程特点

     线程都有一个id

     线程都具有自己的安全属性

     线程都有自己的内存栈

     线程都有自己的寄存器信息

  2.3创建线程:

     HANDLE CreateThread(

        LPSECURITY_ATTRIBUTES lpT,//安全属性

        SIZE_T dwStackSize,//线程栈大小

        LPTHREAD_START_ROUTINE lp,//线程处理函数

        LPVOID lpParameter,//处理函数参数

        DWORD dwCreationFlags,//创建方式

        LPDWORD lpThreadId//线程id    );//线程句柄

        创建标志:0---创建后立刻执行

        CREATE_SUSPENDED-创建之后线程处于挂起

  2.4线程处理函数

    DWORD  WINAPI Threadproc(LPVOID lp){ 参数是需要传入的}

  2.5结束线程

     BOOL TerminateThread(

         HANDLE hThread,//线程句柄

         DWORD dwExitCode//退出码);

    结束当前线程

         VOID ExitThread(DWORD dwExitCode退出码)

    关闭线程句柄:closeHandle

  2.6等候进程结束

     DWORD WaitForSingleObject(

         HANDLE hHandle, //句柄

         DWORD dwMilliseconds//等时间);

    DWORD WaitForMultipleObjects(

          DWORD nCount,//句柄数量

          HANDLE *lpHandles,//句柄地址

          BOOL bWaitAll,//等方式

          DWORD dwMilliseconds//等时间    );

    等待方式:TRUE---所有句柄有信号才结束

              FALSE---一个句柄有信号就返回

//示例代码 (在test中开启多个进程, 然后逐一关闭)

// test10.cpp : 定义应用程序的入口点。
//

#include "stdafx.h"
#include "test10.h"

//进程  通常被定义为一个正在运行的程序的实例
//由两个部分组成:
//1、操作系统用来管理进程的内核对象
//2、地址空间,包含可执行模块,dll模块

//进程和线程   地主和长工
//进程的活动性不强,如果进程要去完成某项操作,它必须拥有一个在它的环境中运行的线程,进程拥有主线程,线程负责
//执行包含在进程的地址空间的代码

//量程  操作系统为每一个线程安排一定的cpu时间,操作系统通过一种循环的方式为线程提供时间片(称为量程)

//进程的特点:1、进程的活动性不强;2、每一个进程都有一个与之匹配的主线程;3、线程才是去执行地址空间的代码

#define MAX_LOADSTRING 100

// 全局变量: 
HINSTANCE hInst;								// 当前实例
TCHAR szTitle[MAX_LOADSTRING];					// 标题栏文本
TCHAR szWindowClass[MAX_LOADSTRING];			// 主窗口类名

// 此代码模块中包含的函数的前向声明: 
ATOM				MyRegisterClass(HINSTANCE hInstance);
BOOL				InitInstance(HINSTANCE, int);
LRESULT CALLBACK	WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK	About(HWND, UINT, WPARAM, LPARAM);

int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
                     _In_opt_ HINSTANCE hPrevInstance,
                     _In_ LPTSTR    lpCmdLine,
                     _In_ int       nCmdShow)
{
	UNREFERENCED_PARAMETER(hPrevInstance);
	UNREFERENCED_PARAMETER(lpCmdLine);

 	// TODO:  在此放置代码。
	MSG msg;
	HACCEL hAccelTable;

	// 初始化全局字符串
	LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
	LoadString(hInstance, IDC_TEST10, szWindowClass, MAX_LOADSTRING);
	MyRegisterClass(hInstance);

	// 执行应用程序初始化: 
	if (!InitInstance (hInstance, nCmdShow))
	{
		return FALSE;
	}

	hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_TEST10));

	// 主消息循环: 
	while (GetMessage(&msg, NULL, 0, 0))
	{
		if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
	}

	return (int) msg.wParam;
}



//
//  函数:  MyRegisterClass()
//
//  目的:  注册窗口类。
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
	WNDCLASSEX wcex;

	wcex.cbSize = sizeof(WNDCLASSEX);

	wcex.style			= CS_HREDRAW | CS_VREDRAW;
	wcex.lpfnWndProc	= WndProc;
	wcex.cbClsExtra		= 0;
	wcex.cbWndExtra		= 0;
	wcex.hInstance		= hInstance;
	wcex.hIcon			= LoadIcon(hInstance, MAKEINTRESOURCE(IDI_TEST10));
	wcex.hCursor		= LoadCursor(NULL, IDC_ARROW);
	wcex.hbrBackground	= (HBRUSH)(COLOR_WINDOW+1);
	wcex.lpszMenuName	= MAKEINTRESOURCE(IDC_TEST10);
	wcex.lpszClassName	= szWindowClass;
	wcex.hIconSm		= LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

	return RegisterClassEx(&wcex);
}

//
//   函数:  InitInstance(HINSTANCE, int)
//
//   目的:  保存实例句柄并创建主窗口
//
//   注释: 
//
//        在此函数中,我们在全局变量中保存实例句柄并
//        创建和显示主程序窗口。
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
   HWND hWnd;

   hInst = hInstance; // 将实例句柄存储在全局变量中

   hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
      CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);

   if (!hWnd)
   {
      return FALSE;
   }

   ShowWindow(hWnd, nCmdShow);
   UpdateWindow(hWnd);

   return TRUE;
}

//
//  函数:  WndProc(HWND, UINT, WPARAM, LPARAM)
//
//  目的:    处理主窗口的消息。
//
//  WM_COMMAND	- 处理应用程序菜单
//  WM_PAINT	- 绘制主窗口
//  WM_DESTROY	- 发送退出消息并返回
//
//

HINSTANCE g_hInst[10];//实例句柄,进程是一个实例
int num = 0;//用来记录进程个数

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	int wmId, wmEvent;
	PAINTSTRUCT ps;
	HDC hdc;

	switch (message)
	{
	case WM_LBUTTONDOWN:
	{
						   if (num < 10)
						   {
							   STARTUPINFOW si;//指定新进程的主窗口特性
							   ZeroMemory(&si, sizeof(si));
							   si.cb = sizeof(si);

							   PROCESS_INFORMATION pi;//进程的相关资源信息

							   switch (num)
							   {
							   case 0:
								   CreateProcess(_T("C:/Program Files (x86)/极速PDF阅读器/JisuPdf.exe"),//创建新进程的路径文件名
									   NULL,//进程命令行参数
									   NULL,//进程是否可以被子进程继承
									   NULL,//线程是否可以被子进程继承
									   FALSE,//新进程是否从调用进程处继承了句柄
									   0,//进程优先级的标记
									   NULL,//指向一个新进程的新环境块
									   NULL,//用来指定子进程的工作路径
									   &si,//用来指定新进程的主窗体如果显示的结构体
									   &pi//指向新进程的识别信息
									   );
								   break;
							   default:
								   //创建进程
								   CreateProcess(_T("C:/Windows/notepad.exe"),//创建新进程的路径文件名
									   NULL,//进程命令行参数
									   NULL,//进程是否可以被子进程继承
									   NULL,//线程是否可以被子进程继承
									   FALSE,//新进程是否从调用进程处继承了句柄
									   0,//进程优先级的标记
									   NULL,//指向一个新进程的新环境块
									   NULL,//用来指定子进程的工作路径
									   &si,//用来指定新进程的主窗体如果显示的结构体
									   &pi//指向新进程的识别信息
									   );
							   }
							   g_hInst[num++] = (HINSTANCE)pi.hProcess;
						   }

	}

		break;
	case WM_RBUTTONDOWN:
		//进程的退出的4种情况:
		//1、入口函数的返回(推荐)
		//2、当前进程调用 TerminateProcess 结束参数中表示的进程
		//3、当前进程调用 ExitProcess 结束当前进程
		//4、进程自动终止(很少见)
		if (num > 0)
		{
			TerminateProcess(g_hInst[--num], 0);
		}
		else
			ExitProcess(0);

		break;
	case WM_COMMAND:
		wmId    = LOWORD(wParam);
		wmEvent = HIWORD(wParam);
		// 分析菜单选择: 
		switch (wmId)
		{
		case IDM_ABOUT:
			DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
			break;
		case IDM_EXIT:
			DestroyWindow(hWnd);
			break;
		default:
			return DefWindowProc(hWnd, message, wParam, lParam);
		}
		break;
	case WM_PAINT:
		hdc = BeginPaint(hWnd, &ps);
		// TODO:  在此添加任意绘图代码...
		EndPaint(hWnd, &ps);
		break;
	case WM_DESTROY:
		PostQuitMessage(0);
		break;
	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
	}
	return 0;
}

// “关于”框的消息处理程序。
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
	UNREFERENCED_PARAMETER(lParam);
	switch (message)
	{
	case WM_INITDIALOG:
		return (INT_PTR)TRUE;

	case WM_COMMAND:
		if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
		{
			EndDialog(hDlg, LOWORD(wParam));
			return (INT_PTR)TRUE;
		}
		break;
	}
	return (INT_PTR)FALSE;
}

 //多线程处理的示例

// test10-thread.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include "windows.h"

//线程:长工
//分成两个部分:内核对象(操作系统用来对线程来进行管理)
//				线程堆栈(用来维护线程当中运行的时候需要的函数的参数和临时变量)

//线程可以理解为一个代码的执行序列,在某个进程的环境中创建,所以寿命也在该进程当中

//线程所需要的开销要比进程要少,在编程的过程,尽量通过增加线程来解决程序问题(多线程)
//量程-cpu时间片-分配给线程

//多线程:就是用来同时处理多个任务,异步处理多个代码序列

int num = 10;//票的数量及票的id

void aa(LPVOID v)
{
	while (true)
	{
		if (num > 0)
		{
			printf("第%d个代售点卖出第%d张票\n", (int)v, num);
			//num--;
			num = num - 1;
		}
		else
			break;
	}
}

int _tmain(int argc, _TCHAR* argv[])
{
	int id = 1;
	HANDLE hThread1,hThread2,hThread3,hThread4;
	hThread1 = CreateThread(NULL,
		0,//线程堆栈大小,默认0
		(LPTHREAD_START_ROUTINE)aa,//线程处理的函数
		(LPVOID)id,//传递给线程函数的辅助信息
		0,//线程创建时的参数,默认给0
		NULL//线程ID
		);
	id++;
	hThread2 = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)aa,(LPVOID)id,0,NULL);
	id++;
	hThread3 = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)aa, (LPVOID)id, 0, NULL);
	id++;
	hThread4 = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)aa, (LPVOID)id, 0, NULL);

	while (true)
	{
		if (num > 0)
		{
			printf("火车站卖出第%d张票\n", num);
			num--;
		}
		else
			break;
	}

	//线程的退出的4种情况:
	//1、函数的返回(推荐)
	//2、当前线程调用 TerminateThread 结束参数中表示的线程
	//3、当前线程调用 ExitThread 结束当前线程
	//4、线程自动终止(很少见)

	TerminateThread(hThread1,0);
	TerminateThread(hThread2, 0);
	TerminateThread(hThread3, 0);
	TerminateThread(hThread4, 0);

	return 0;
}

 

 

 

三.线程同步

3.1原子锁

  对单条指令进行操作,当多个线程同时对同一个资源进行操作的时,由于线程间资源的抢占,会导致操作的结果丢失或者不是我们预期的。例如a++

     interLockedIncrement、加

     interlockedDecrement减

     InterlockedExchange交换

3.2临界区

     CRITICAL_SECTION cs = { 0 };

     InitializeCriticalSection() //

     EnterCriticalSection( &cs );

     LeaveCriticalSection( &cs );

     DeleteCriticalSection( &cs );

3.3事件

    3创建事件:

    HANDLE CreateEvent(

      LPSECURIT utes,// 安全属性

      BOOL bManualReset,//重置方式false自动 true手动

      BOOL bInitialState,//初始状态ture有信号

      LPCSTR lpName//事件名

   );成功返回事件句柄

  等候事件 waitforsingleobject/xxxMultiple

  触发事件: setEvent(事件句柄)

            resetEvent()

  关闭事件CloseHandle

3.4 互斥

    HANDLE CreateMutex(

       LPSECURITY_A butes,//安全属性

       BOOL bInitialOwner,//初始拥有者

       LPCSTR lpName//互斥命名);

    拥有者:调用createMtex的拥有互斥

    False 创建的时候没有线程拥有互斥

    等候互斥 WaitForSingleObject

    释放互斥ReleaseMutex(互斥量)

    关闭句柄 closehandle

3.5 信号量

  3.5.1创建信号量

    HANDLE CreateSemaphore(

      LPSECURITY_ATTRIBUTES lpSees,//安全属性

      LONG lInitialCount,//初始化信号量数量

      LONG lMaximumCount,//信号量最大值

      LPCSTR lpName//命名);

    等候信号量:等候通过一次 信号量的信号减一,0堵塞

  3.5.2释放信号量

  BOOL ReleaseSemaphore(

    HANDLE hSemaphore,//信号量句柄

    LONG lReleaseCount,//释放数量

    LPLONG lpPreviousCount//释放前数量    );

  关闭句柄+

示例代码 :如下

// test11.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <windows.h>

//线程通讯
//线程需要在两种情况下需要通讯
//1、如果有多个线程访问共享资源而不能够让这个共享资源被破坏
//2、当一个线程将某个任务完成,通知另一个线程接着完成这一系列的任务时


//线程互斥
//一、用户区间	二、内核对象(并不意味只能做线程互斥)

//一、用户区间
//临界区


临界区  1、定义一个临界区的变量
//CRITICAL_SECTION  g_cs;

//二、内核对象
事件		基于内核对象的同步机制
进程和线程也属于内核对象,对于内核来说每种对象都可以处于已通知和未通知两种状态,状态的切换是由系统规则来决定
当进程在运行的过程中,进程内核对象处于未通知状态,当进程结束时,变为已通知
事件	1、事件句柄定义
//HANDLE g_hEvent;

互斥内核对象		能够确保线程对于单个资源的互斥访问权
互斥内核对象包含一个使用数量,一个线程id和一个递归的计数器
互斥对象的行为特性和临界区相同,互斥属于内核,临界区属于用户
id用于标识系统中哪个线程当前拥有互斥对象,递归计数器用于指明该线程拥有互斥对象的次数
互斥对象的使用规则:如果线程id为0,这代表无效id,互斥对象没有被线程拥有,互斥对象发出通知信号,id非0,证明有
线程在拥有互斥,不发出信号
//
互斥对象		1、互斥对象的句柄定义
//HANDLE g_hEutex;

//信号量	信号量用于对资源进行计数
//信号量的使用规则:
//1、当前的资源数量大于0,可以去使用资源,发出信号
//2、当前的资源数量是0,没有可使用资源,不发出信号
//3、系统不允许资源数量为负数
//4、当前可以使用的资源数量不能大于最大的资源数量
//信号量		1、句柄的定义
HANDLE		g_hSemaphore;




int num = 50;

void aa(LPVOID v)
{
	while (true)
	{
		临界区	3、进入临界区
		//EnterCriticalSection(&g_cs);

		事件	3、等待通知
		//WaitForSingleObject(g_hEvent, INFINITE);//使用线程自愿等待,等待到已通知

		互斥		3、等待通知
		//WaitForSingleObject(g_hEutex, INFINITE);//使用线程自愿等待,等待到已通知

		//信号量		3、等待通知
		WaitForSingleObject(g_hSemaphore, INFINITE);//使用线程自愿等待,等待到已通知

		if (num > 0)
		{
			printf("第%d个代售点卖出%d张票\n",(int)v,num);
			num--;
		}
		else
		{
			临界区	4、离开临界区
			//LeaveCriticalSection(&g_cs);

			事件	4、设置事件通知
			//SetEvent(g_hEvent);

			互斥		4、重置互斥内核
			//ReleaseMutex(g_hEutex);

			//信号量		4、重置信号量
			//第2个参数为重置当前的资源数量
			ReleaseSemaphore(g_hSemaphore, 1, NULL);
			break;
		}

		临界区	4、离开临界区
		//LeaveCriticalSection(&g_cs);

		事件	4、设置事件通知
		//SetEvent(g_hEvent);

		互斥		4、重置互斥内核
		//ReleaseMutex(g_hEutex);

		//信号量		4、重置信号量
		//第2个参数为重置当前的资源数量
		ReleaseSemaphore(g_hSemaphore, 1, NULL);
	}
}

int _tmain(int argc, _TCHAR* argv[])
{
	临界区	2、临界区变量的初始化
	//InitializeCriticalSection(&g_cs);

	事件	2、事件的初始化
	第2个参数表示是自动事件(自动重置)还是手动事件(人工重置),人工重置的事件得到通知的时候,等待这个事件的所
	有线程都已通知,false代表人工重置
	第3个参数代表着已通知
	//g_hEvent = CreateEvent(NULL, false, true, NULL);

	互斥		2、互斥的初始化
	第2个参数为false,代表互斥对象拥有的id和计数器都为0,给true,代表互斥对象的id为调用线程的id,计数器加1
	(id为0,代表互斥对象没有线程占用,发出通知信号,如果id非0,证明有线程拥有这个互斥对象,不发出通知信号)
	//g_hEutex = CreateMutex(NULL, false, NULL);

	//信号量		2、信号量的初始化
	//第2个参数是初始化资源数量,如果大于0,发出通知信号,如果资源为0,不发出信号,不允许为负数,不允许超过第3个参数
	//第3个参数是最大的资源数量
	g_hSemaphore = CreateSemaphore(NULL, 1, 1, NULL);

	int id = 1;
	HANDLE hThread1 = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)aa, (LPVOID)id, 0, NULL);
	id++;
	HANDLE hThread2 = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)aa, (LPVOID)id, 0, NULL);
	id++;
	HANDLE hThread3 = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)aa, (LPVOID)id, 0, NULL);
	id++;
	HANDLE hThread4 = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)aa, (LPVOID)id, 0, NULL);

	while (true)
	{
		临界区	3、进入临界区
		//EnterCriticalSection(&g_cs);

		事件	3、等待通知
		//WaitForSingleObject(g_hEvent, INFINITE);//使用线程自愿等待,等待到已通知

		互斥		3、等待通知
		//WaitForSingleObject(g_hEutex, INFINITE);//使用线程自愿等待,等待到已通知

		//信号量		3、等待通知
		WaitForSingleObject(g_hSemaphore, INFINITE);//使用线程自愿等待,等待到已通知

		if (num > 0)
		{
			printf("火车站卖出%d张票\n",  num);
			num--;
		}
		else
		{
			临界区	4、离开临界区
			//LeaveCriticalSection(&g_cs);

			事件	4、设置事件通知
			//SetEvent(g_hEvent);

			互斥		4、重置互斥内核
			//ReleaseMutex(g_hEutex);

			//信号量		4、重置信号量
			//第2个参数为重置当前的资源数量
			ReleaseSemaphore(g_hSemaphore, 1, NULL);
			break;
		}

		临界区	4、离开临界区
		//LeaveCriticalSection(&g_cs);

		事件	4、设置事件通知
		//SetEvent(g_hEvent);

		互斥		4、重置互斥内核
		//ReleaseMutex(g_hEutex);

		//信号量		4、重置信号量
		//第2个参数为重置当前的资源数量
		ReleaseSemaphore(g_hSemaphore, 1, NULL);
	}


	临界区	5、释放临界区
	//DeleteCriticalSection(&g_cs);

	事件	5、关闭事件
	//CloseHandle(g_hEvent);

	互斥	5、关闭互斥
	//CloseHandle(g_hEutex);

	//信号量	5、关闭信号量
	CloseHandle(g_hSemaphore);

	DWORD threadExitCode1, threadExitCode2, threadExitCode3, threadExitCode4;
	while (true)
	{
		GetExitCodeThread(hThread1, &threadExitCode1);//得到hThread1线程的退出码,把退出码放在threadExitCode1里面
		GetExitCodeThread(hThread2, &threadExitCode2);
		GetExitCodeThread(hThread3, &threadExitCode3);
		GetExitCodeThread(hThread4, &threadExitCode4);
		//STILL_ACTIVE 代表线程或者进程仍然活跃
		if (threadExitCode1 != STILL_ACTIVE && threadExitCode2 != STILL_ACTIVE
			&& threadExitCode3 != STILL_ACTIVE && threadExitCode4 != STILL_ACTIVE)
			break;
	}
	TerminateThread(hThread1, 0);
	TerminateThread(hThread2, 0);
	TerminateThread(hThread3, 0);
	TerminateThread(hThread4, 0);

	return 0;
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值