WINDOWS — 基于C/C++的线程操作详解(一)
2020/11/28
为了了解WINDOWS下的线程API接口使用方法,首先得知道以下几个知识点。
一.什么是进程?
官方解释:
简单解释:
电脑上的一个程序一运行就是一个进程的创建,打开任务管理器,里面就可以看到各个进程的占用内存,CPU,磁盘等信息。
二.什么是线程?
官方解释:
线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
简单解释:
抽象一点来说,就等于是高铁运做是一个进程,高铁上有驾驶员,服务员,每个人做自己要做的事,他们就是线程。
简单了解完进程和线程,现在开始来了解基于C语言的线程操作:
既然是对于线程有关的操作,首先我们得知道我们是在一个什么样的环境下编译代码执行操作。
我使用的是Visual Studio,,x64 是 WIN64环境(Microsoft Windows操作系统的64位环境),x86就是WIN32环境。
了解完编译环境,来看看几个接下来要用到的几个基本的变量类型。
HANDLE,DWORD, WORD,BYTE, LPVOID ,我们来一个一个解释。
首先是HANDLE,什么是HANDLE,可以在VS中选中HANDLE类型,F12查看一下,可以找到如下定义: typedef void *HANDLE,这样就很明显了,HANDLE是一个无类型的指针,那么我们要这么一个无类型的指针来做什么呢,其实在WIN32的帮助文档里面,解释的非常简洁明了:"A variable that identifies an object; an indirect reference to an operating system resource."
翻译来就是:一个识别对象的变量;对操作系统资源的间接调用。
HANDLE的中文意思是“句柄”,其实我们可以这样理解更为简单,HANDLE是一个帮你找到"指向操作系统资源的指针"的东西,这样的好处是,我们不需要直接使用指向操作系统的指针去调用资源,这样就防止了我们的抠脚操作对操作系统造成无法预料的破坏,而用一个HANDLE去间接调用,在安全范围内保证你无法对操作系统造成破坏,但你又能有一定方法去使用操作系统的资源。就比如说,我们创建了一个线程,线程属于操作系统中的一个资源,那么创建好后我们怎么去对线程进行操作呢,这时HANDLE的作用不就体现出来了。
然后是DWORD,其实到编译器里一查看DWORD定义,就能知道WORD和BYTE的定义了
typedef unsigned long DWORD;
typedef unsigned char BYTE;
typedef unsigned short WORD;
这里很清晰的可以看到 DWORD = unsigned long (无符号长整形)
BYTE = unsigned char (无符号整形)
WORD = unsigned short(无符号短整型)
LPVOID 类型:无类型指针,任何类型的指针都可以赋值给LPVOID类型的变量,然后使用时再转回来。
可以传任何类型的值。
现在已经具备基本线程操作知识了,接下来在VS里面创建一个线程。
我们要用到 windows中的 CreateThread() 接口,来创建一个线程,我们说了,创建线程后肯定要对线程进行一系列操作,那么我们要用到HANDLE变量去操作,所以在使用CreateThread()函数时,应该用一个HANDLE变量去指向它。
先不提这些,我们先把CreatThread()的参数弄明白,才能去使用。
这里我做了个简单的释义:
1 HANDLE CreateThread( 2 LPSECURITY_ATTRIBUTES lpThreadAttributes,//SD:线程安全相关的属性,常置为NULL 3 SIZE_T dwStackSize,//initialstacksize:新线程的初始化栈的大小,可设置为0 4 LPTHREAD_START_ROUTINE lpStartAddress,//threadfunction:被线程执行的回调函数,也称为线程函数 5 LPVOID lpParameter,//threadargument:传入线程函数的参数,不需传递参数时为NULL 6 DWORD dwCreationFlags,//creationoption:控制线程创建的标志 7 LPDWORD lpThreadId//threadidentifier:传出参数,用于获得线程ID,如果为NULL则不返回线程ID 8 ) 9 */
有人好奇新线程的初始化栈的大小设为0位怎么样,不会怎么样,因为设为0只是告诉它我们要创建一个默认大小的初始栈,而默认大小为1MB,也就是1024*1024个字节.
一个线程需要具备什么,当然需要一个执行的任务过程,不然空线程没有任何意义,回调函数就是该线程需要执行的东西。
回调函数需要满足什么,必须包括一个LPVOID的参数,然后满足WINAPI要求,所以创建回调函数一般是如下格式:
DWORD WINAPI 函数名 (LPVOID 参数名){}
线程回调函数一般必须是全局函数(特殊情况下可以设置为类成员函数)
之前也说了要用一个HANDLE变量去操作线程,所以一般流程如下:
1. HANDLE operate_thread;
2. operate_thread = CreateThread(NULL,0,func,(LPVOID)argv_test,0,NULL);
之后,要对这个线程操作都会用到这个operate_thread,相当于万能钥匙。
接下来我们在C语言基础上完完整整的创建一个线程吧。
(还有一个前提,因为都是基于WINDOWS操作系统上操作,所以需要包括头文件<windows.h>,如果是Linux则是<Phtreads.h>)
1 #include<windows.h> 2 #include<iostream> 3 using namespace std; 4 5 DWORD WINAPI func(LPVOID lpParam) { //回调函数 6 for (int i = 0; i < 10; i++) { 7 cout << "The sub-Thread running.." << endl; 8 Sleep(500); 9 } 10 return 0; 11 } 12 13 int main(int argc, const char **argv) { 14 int argv_test = 10;//传给回调函数的参数 15 HANDLE th = CreateThread(NULL, 0, func,(LPVOID)argv_test, 0, NULL);//创建句柄用来之后操作线程。 16 CloseHandle(th);//如果之后都用不到这个句柄,就直接释放 17 for (int i = 0; i < 10; i++) { 18 cout << "main thread running.." << endl; 19 Sleep(500); 20 } 21 22 system("pause"); 23 return 0; 24 }
(注:main函数也算一个线程,算主线程)
这样一个简单的线程操作程序就完成了。