PoEdu - Windows阶段班 【Po学校】Lesson06 线程
win32API 线程
-
线程 函数
-
复习:进程启动
- 进程创建之始,会新建一个“进程内核对象”,此对象包含有进程的许多参数,如:进程地址空间;进程是一堆数据组合,它有一定的惰性。
- 接着会启动一个线程,线程才是CPU执行的基础单位。
-
在进入main函数启动之前,就已经准备好了堆栈,启动好了线程。
- 本质上:main函数也是一个线程函数,它符合线程运行的规则:线程运行前,操作系统要知道,线程从哪个函数开始运行。此时默认找到当前函数,线程运行开动。
-
线程 堆栈
- 入口函数是编译器帮我们创建完成的线程函数
- 一般线程,需要CreateTread()函数创建线程,同时创建一个“线程内核对象(结构体)”,操作系统通过此结构体来管理线程。
- 接着会在当前进程空间内,创建一块空间,将此空间当作当前线程的堆栈。
-
线程创建所需参数
-
第1个参数:线程继承相关属性
- 进程之间继承关系的本质:
-
第2个参数 堆大小的设置
- 默认线程堆栈大小1MB
- 当默认的1MB或者自定义的大小不够用时,会抛出栈溢出,被当前程序捕获后,再次分配更多的空间,这是一个动态分配的机制。
- 有一种情况下,会导致栈溢出:当一次性需要的堆栈空间过大,1MB不够用的时候,第2次分配时,还是需要大于1MB空间,会抛出栈溢出错误。
- 两种解决方式 : 1 在CreateThread()函数中,把初始保留堆栈大小设置得更大;
- 2 把需要一次性使用的堆栈,设置得小一些,通过分批分次的方式使用初始保留堆栈空间。
-
第3个参数 线程开始的地址,一般是某个函数的地址。
- 线程的入口函数,必须符合以下几点:
- 1 必须 是一个stdcall
- 2 必须返回一个DWORD
- 3 必须带一个LPVOD参数,这个参数可以在紧接着的第4个参数传递。
-
第4个参数 传递给第3个参数(也就是回调函数),注意传递之间参数的生命周期。
-
第5个参数 标志位,0表示 创建成功后,直接运行; CREATE_SUSPENDED表示暂停。
-
第6个参数 传递一个ThreadID
- 主线程退出与其他线程退出 之间的区别:
- 主线程退出,进程消亡,会清理所有线程。
- 一般的线程(非主线程)退出,其子线程不会随之消亡,要在运行完成后才消亡。
- 一般的线程与一般子线程之间抢占CPU资源。
- 一般线程的正确使用 7步
-
多线程中参数的传递,需要格外的注意:父进程的消亡,子进程还存在时,参数的生命周期是否有保障
-
深入理解时间片 (实验)
#include <windows.h> #include <tchar.h> enum ThreadSign { NO1, NO2, NO3 }; ThreadSign g_ThreadSign; DWORD WINAPI ThreadFuncNo1(LPVOID lParama) { for (int i = 1; i <=100 ; ++i) { while (g_ThreadSign != NO1) { Sleep(1); } _tprintf(TEXT("No1:%d\r\n"), i); g_ThreadSign = NO2; } return 0; } DWORD WINAPI ThreadFuncNo2(LPVOID lParama) { for (int i = 101; i <= 200; ++i) { while (g_ThreadSign != NO2) { Sleep(1); } _tprintf(TEXT("No2:%d\r\n"), i); g_ThreadSign = NO3; } return 0; } int main() { HANDLE hThread[2]; hThread[0] = CreateThread(nullptr, 0, ThreadFuncNo1, nullptr, 0, nullptr); hThread[1] = CreateThread(nullptr, 0, ThreadFuncNo2, nullptr, 0, nullptr); g_ThreadSign = NO1; for (int i = 201; i <= 300; ++i) { while (g_ThreadSign != NO3) { Sleep(1); } _tprintf(TEXT("No3:%d\r\n"), i); g_ThreadSign = NO1; } WaitForMultipleObjects(2,hThread,TRUE,INFINITE); for (int i = 0; i < sizeof(hThread)/sizeof(hThread[0]); ++i) { CloseHandle(hThread[i]); } return 0; }
-
线程的退出:当一个线程结束时,会发生哪些事情?
- 先来看 进程 的销毁:
- 销毁临时对象
- 释放堆栈
- 将返回值设置为我的退出代码
- 减少进程内核对象的使用计数
- 实际上线程的销毁与进程的销毁相同的。当一个 线程 销毁时:
- 销毁临时对象,调用我们的析构函数
- 释放线程里面所有分配的堆栈
- 将线程的入口函数的返回值设置为我们的退出代码
- 减少线程内核对象的使用计数
- 先来看 进程 的销毁:
-
线程退出函数:
-
-
- ExitThread 立即结束当前线程
- 终止线程运行,销毁堆栈
- 并释放以下资源:(窗口句柄 与 HOOK对象是线程的标配)
- 1 如有创建窗口,则还释放窗口句柄;
- 2 HOOK(勾子)对象。
- 不调用析构函数,导致内存泄漏
- TerminateThread 可以结束其他的线程;此函数的动作,与ExitThread()函数销毁动作类似。
- 回头看线程启动中,调用了哪些资源?
- 注意一点:线程没有自己的内存空间,内存空间是进程所属。线程在启动时,去进程当中申请一块内存,作为当前线程的栈;
- 创建之初,构建一个线程内核对象,线程内核对象的结构中,包含与进程相同的参数:1 使用计数,2 退出代码(ExitCode)等。但线程会多出一个信号参数----受信状态:Signaled(信号),用以调控线程的执行优先顺序。
- 另外在结构体之外,还有一个CONTEXT(线程上下文)参数,存储了当前CPU寄存器的状态: IP(指定寄存器:下一条指令是什么)、SP(栈寄存器:从哪个地址执行);
- 线程在创建之初,还有两个参数是不可或缺的:
- lParam
- lpStartAddress 线程的入口函数地址
- ExitThread 立即结束当前线程
-