WINDOWS — 基于C/C++的线程操作详解(一)

2020/11/28 

为了了解WINDOWS下的线程API接口使用方法,首先得知道以下几个知识点。

一.什么是进程?

官方解释:

狭义定义---进程是正在运行的程序的实例(an instance of a computer program that is being executed)。
广义定义---进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。

简单解释:

电脑上的一个程序一运行就是一个进程的创建,打开任务管理器,里面就可以看到各个进程的占用内存,CPU,磁盘等信息。

二.什么是线程?

官方解释:

线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

简单解释

抽象一点来说,就等于是高铁运做是一个进程,高铁上有驾驶员,服务员,每个人做自己要做的事,他们就是线程。

简单了解完进程和线程,现在开始来了解基于C语言的线程操作:

既然是对于线程有关的操作,首先我们得知道我们是在一个什么样的环境下编译代码执行操作。

我使用的是Visual Studio,,x64 是 WIN64环境(Microsoft Windows操作系统的64位环境),x86就是WIN32环境。

了解完编译环境,来看看几个接下来要用到的几个基本的变量类型。

HANDLEDWORD, 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函数也算一个线程,算主线程)

这样一个简单的线程操作程序就完成了。

原文地址:https://www.cnblogs.com/xiangqi/p/14052252.html