线程
线程函数:
1:每个进程启动的时候会新建一个进程内核对象(代表分配的一块空间),然后会创建一个线程。
2:在main函数执行前,mainStartup已经启动了线程,启动线程之后,线程就会对我们的函数进行调用,具体调用哪个函数,是可以设置的,可以将入口函数不设置为main函数,可以更改为自己喜欢的函数名(在属性 -> 高级 -> 入口点 这里设置)。所以main函数也是一个线程函数,线程在启动的时候,必须告诉线程要来使用哪个函数进行运行, 此时线程在运行的时候就会找到当前的函数,然后向下运行。
线程堆栈:
1:main函数是主线程的入口函数,如果自己要来创建一个线程的话,我们需要在一个县城里面来调用CreateThread,他会创建一个线程内核对象,和进程内核对象一样,它仅仅是一个结构体,并不代表线程(进程)本身,主要用来操作系统,对线程进行管理。
2:创建线程内核对象后,也会分配一块空间,这块空间是当前线程的堆栈(用于记录),每一个线程都有自己的堆栈。
3:线程在一定角度来说是相互分开的,每个线程都有自己的堆栈,但是堆和栈都属于当前这个进程,在进程中,任何一块内存都拥有访问权限,这就赋予了两个线程之间数据交互的可能。以进程的方式来访问多个线程的数据,这称为线程间数据的同步。
堆栈大小设置:
1:CreateThread():
HANDLE WINAPI CreateThread(
_In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes, //指向SECURITY_ATTRIBUTES 结构的指针,用于确定返回的句柄是否可以由子进程继承。如果 lpThreadAttributes为NULL,则不能继承该句柄。
_In_ SIZE_T dwStackSize, //栈大小(单位byte),如果传入0,则为默认大小(1M)
_In_ LPTHREAD_START_ROUTINE lpStartAddress, //指向由进程执行的应用长须定义函数的指针。表示线程的起始地址。
_In_opt_ LPVOID lpParameter, //指向要传递给线程的变量的指针,传递给线程。
_In_ DWORD dwCreationFlags, //控制线程创建的标志
_Out_opt_ LPDWORD lpThreadId //指向接收线程标识符的变量的指针。如果此参数为 NULL,则不返回线程标识符。
);
2:如果希望子系统能够继承当前这个线程对象的句柄,姐必须创建一个LPSECURITY_ATTRIBUTES结构体,并将里面的bInheritHandle参数设置为TRUE。
例如:ring3层里面有很多的进程,包含A和B进程,A进程是B进程的父进程,在ring0层里面,有A进程的内核对象,A进程里面各个线程的内核对象,B进程继承了A进程的某个线程对象,实际就是B进程拥有了A进程里面一个线程内核对象的访问权限。
在内核里面,所有内核对象(进程/线程)都是平行的关系,他们都是同一个级别的内核对象,不是包含的关系。仅是在ring3用户层看起来才是包含的关系。
3:dwStackSize,栈大小,如果使用默认值,栈的使用过程中超过默认值,就会抛出栈溢出的异常,然后重新分配栈的大小,如果一次性分配栈空间非常大导致自动分配后还是不够用,他还会将异常抛出来。
线程入口函数:
1:lpStartAddress入口函数是一个回调函数,其返回值必须是一个DWORD类型,并且里面的参数为LPCOID。函数前必须为__stdcall。
2:lpParameter传递给线程的参数,我们可以传递一个对象的地址,数值等,方便区别使用。
3:dwCreationFlags,可以传递0,代表CreateThread后直接运行,传递CREATE_SUSPENDED的话,CreateThread后不会直接运行,还需调用ResumeThread后才开始运行。
4:lpThreadId:传递ThreadID,需要接受就传递DWORD类型,不需要接收就传递NULL。一般传递NULL即可。
主线程退出和其他线程退出的区别:
1:当主线程执行到return 0的时候,他会结束我们的其他线程,测试如下:
#include <iostream>
#include <windows.h>
#include <tchar.h>
DWORD __stdcall MyMain(LPVOID param)
{
int nParam = (int)param;
Sleep(1); //当有Sleep的时候,可能下面的就打印不到了,没有Sleep可能就会打印出来。
_tprintf(TEXT("nParam = %d"), nParam);
return 0;
}
int main()
{
std::cout << 0 << std::endl;
HANDLE ThreadHandle = CreateThread(nullptr, 0, MyMain, (LPVOID)0xFFFFFFFF, 0, nullptr);
// Sleep(100);
if (ThreadHandle)
CloseHandle(ThreadHandle);
return 0;
}
2:线程执行和进程很大的区别就在这里,A进程启动B进程,A进程结束了B进程还在,但是线程就会不一样,主线程启动的子线程,在主线程结束的时候,子线程也会结束。同理,子线程再启动一个子线程,如果当前子线程消亡,主线程还未消亡的话,子线程的子线程还是可以正常运行。
#include <iostream>
#include <windows.h>
#include <tchar.h>
DWORD __stdcall MyMain1(LPVOID param)
{
int nParam = (int)param;
// Sleep(100); //如果有Sleep(100),则会先打印-1,后打印100,否则先打印100,后打印-1。
_tprintf(TEXT("nParam = %d"), nParam);
return 0;
}
//从两个子线程的不同执行顺序来看,只要整个程序的主线程未消亡,所有子线程都不会消亡。
DWORD __stdcall MyMain(LPVOID param)
{
int nParam = (int)param;
HANDLE ThreadHandle = CreateThread(nullptr, 0, MyMain1, (LPVOID)0x64, 0, nullptr);
if (ThreadHandle)
CloseHandle(ThreadHandle);
Sleep(20);
_tprintf(TEXT("nParam = %d"), nParam);
return 0;
}
int main()
{
std::cout << 0 << std::endl;
HANDLE ThreadHandle = CreateThread(nullptr, 0, MyMain, (LPVOID)0xFFFFFFFF, 0, nullptr);
Sleep(10000);
if (ThreadHandle)
CloseHandle(ThreadHandle);
return 0;
}
3:主线程退出,所有的子线程都会推出是因为进程消亡的时候,所有的线程也会消亡。