线程基础、线程调度
1、线程的两个组成部分:线程的内核对象(管理线程和使用计数)、线程栈(用于维护线程执行时所需的函数参数和局部变量)
2、线程函数:
线程函数可以任意命名;
线程函数只有一个参数;
线程函数必须返回一个值,它会成为该线程的退出代码;
线程函数应尽可能使用函数参数和局部变量;
3、线程终止运行时:
线程拥有的所有用户对象(窗口、挂钩)会被释放,其他对象只有在拥有线程的进程终止时才被销毁;
线程的退出代码(在内核对象中维护)从STILL_ACTIVE变成传给ExitThread或TerminateThread的代码;
线程内核对象的状态变为触发状态;
如果线程是进程的最后一个活动线程,系统则认为进程也终止了;
线程内核对象的使用计数递减1;
4、线程的上下文:一组CPU寄存器,最重要的两个寄存器是指令指针寄存器IP和栈指针寄存器SP,IP被设为线程函数地址在线程堆栈中的地址,SP被设为RtlUserThreadStart函数的地址,该函数会调用ExitThread和ExitProcess函数
5、在多线程环境中C/C++运行库的变量和函数会出问题,为保证多线程程序正常运行,必须创建一个数据结构,并使之与使用了C/C++运行库函数的每个线程关联,然后在调用C/C++运行库函数时,那些函数必须知道去查找主调线程的数据块,从而避免影响到其他线程
6、_beginthreadex函数:
每个线程都有自己的专用_tiddata内存块,它们是从C/C++运行库的堆上分配的;
传给_beginthreadex的线程函数的地址保存在_tiddata内存块中;
_beginthreadex会在内部调用CreateThread;
CreateThread函数被调用时,传给他的函数地址是_threadstartex(而非pfnStartAddr),参数地址是_tiddata结构的地址(而非pvParam);
成功返回线程句柄,失败返回0
7、_threadstartex函数:
新的线程先执行RtlUserThreadStart,再跳转到_threadstartex;
_threadstartex唯一参数是新线程的_tiddata内存块的地址;
_threadstartex函数将_tiddata内存块与新建线程关联起来,调用操作系统函数TlsSetValue(线程局部存储TLS);
无参数的辅助函数_callthreadstartex中有一个SEH帧,它将预期要执行的线程函数包围起来;
预期要执行的线程函数会被调用,其地址和参数会在_callthreadstartex中从TLS中获取;
_callthreadstartex中调用_endthreadex函数,最后返回到_threadstartex,继而到RtlUserThreadStart,线程终止运行
8、_endthreadex函数
函数将_tiddata内存块释放,再调用ExitThread函数销毁线程,并传递设置线程的退出代码
9、使用CreateThread创建线程会导致的问题:
线程如果使用C/C++运行库的signal函数,则整个进程都会终止,因为结构化异常处理SEH帧没有就绪;
线程如果不是通过调用_endthreadex来终止,则数据块就不能被销毁,从而导致内存泄漏
10、线程的挂起和恢复:SuspendThread、ResumeThread
11、Sleep函数将使线程自己挂起一段时间:
系统设置线程不可调度时间只是近似于所设定的毫秒数;
参数传入INFINITE时告诉系统永远不要调度该线程,传入0时是强制系统调度其他线程
12、GetThreadTimes函数可返回线程的创建时间、退出时间、内核时间、用户时间,线程执行时间 = 内核时间+用户时间
GetProcessTimes类似GetThreadTimes,返回时间适用于一个指定进程中的所有线程所耗时间的总和
13、系统使用CONTEXT结构记住线程的状态,这样线程在下一次获得CPU可以运行时,就可以从上次停止处继续。
GetThreadContext函数只能返回线程的用户模式上下文,可以查看线程的任何寄存器值,再调用该函数前应先调用SuspendThread