线程由两部分组成:
线程内核对象, 系统用此来管理线程,存放线程统计信息和线程上下文(指令指针寄存器、栈指针寄存器、其他存储器)。
线程栈, 用来维护线程执行时所需要的局部变量和函数的参数。
创建线程和创建进程的优劣:
进程与线程相比,进程耗费的系统资源更多,原因在于地址空间,系统会为进程创建一个虚拟的地址空间。
创建进程会发生大量记录活动,而线程不会涉及记录活动,所以创建进程会用到大量内存。
由于一个.exe 和 .dll文件要加载到一个地址空间,还要用到文件资源。
线程处理函数:
DWORD WINAPI ThreadProc ( PVOID pvParam) {
DWORD result = 0;
return result;
}
线程函数名可以修改,线程函数应尽量使用函数参数和局部变量,因为使用静态变量和全局变量时,多个线程可以同时访问这些变量,这样有可能会破坏变量的内容。如果必须要使用静态或全局变量,可以参考线程同步的内容。
创建线程:
HANDLE WINAPI CreateThread( __in LPSECURITY_ATTRIBUTES lpThreadAttributes, __in SIZE_T dwStackSize, __in LPTHREAD_START_ROUTINE lpStartAddress, __in LPVOID lpParameter, __in DWORD dwCreationFlags, __out LPDWORD lpThreadId );
lpThreadAttributes:指向一个SECURITY_ARRTIBUTES结构的一个指针,用做线程的安全属性,可以设置线程句柄的继承特性。
dwStackSize: 指定为其线程分配多少地址空间。如果传入0, 系统会根据/STACK链接开关指定的存储量来调拨存储器
lpStartAddress: 线程处理函数的地址
lpParameter: 线程处理函数的参数
dwCreationFlags: 线程创建成功后的标志,0为立即调度,CREATE_SUSPENDED为暂停调度,等待调用ResumeThread()函数,线程开始调度。
lpThreadId:返回线程id。可以传NULL,以表示不使用
函数返回线程内核对象句柄。
uintptr_t _beginthreadex( void *security,
unsigned stack_size,
unsigned ( *start_address )( void * ),
void *arglist,
unsigned initflag,
unsigned *thrdaddr );
函数参数与函数返回值与CreateThread一致
CreateThread函数与_beginthreadex函数的区别:
CreateThread函数是windows运行库的函数,而_beginthreadex是c/c++运行库的函数,每一个线程都有自己的专用_tiddata数据块.当用CreateThread创建线程时,线程不会分配这个数据块,而当线程调用一个需要_tiddata结构的c/c++运行库函数时,c/c++运行库函数会尝试取得线程数据块的地址(调用TlsGetValue).如果NULL被作为地址返回,表明主调线程没有相关联的_tiddata数据块。这时,c/c++运行库函数会为线程分配并初始化一个_tiddata块,然后与线程关联(调用TlsSetValue)。当用_beginthreadex创建线程时,_beginthreadex内部会先分配一个_tiddata数据块,并将线程函数地址和参数传给_tiddata数据块,然后调用CreateThread创建线程,为其线程函数传递_threadstartex函数,线程参数传递_tiddata数据块。而在_threadstartex函数中,将_tiddata数据块与线程想关联,然后调用_callthreadstartex函数,函数内部做了结构化异常处理(SEH),调用_tiddata数据保存的函数地址和参数以调用外部定义的线程函数。
最好用_beginthreadex函数而不用CreateThread函数的目的在于:当用CreateThread创建线程时
第一,如果线程使用了c/c++运行库的signal函数,进程会终止,因为SEH没有就绪。
第二,如果线程不是使用_endthreaex来终止的,_tiddata数据块将不会被释放,造成内存泄露。
终止线程:
终止线程有四种方式:
线程函数返回:当线程函数返回时,线程内的所有资源会被正确清理。c++对象通过析构函数被正确销毁,释放线程使用内存,设置线程退成代码,减少线程内核对象使用计数
调用ExitThread函数:主调线程立即终止,操作系统清理线程占用系统资源。参数DWORD为线程退出代码,内核对象使用计数递减。(此为windows运行库版本,应使用c/c++运行库的_endthreadex,原因如CreateThread 函数和_beginthreaex函数的区别一样)
调用TerminateThread函数:工作模式同ExitThread函数一样,区别在于可以终止任何线程,线程的堆栈不会被销毁,函数是异步的。即函数返回时,线程不一定已经停止运行。
进程被终止(即ExitProcess函数和TerminateProcess函数):两个函数将会终止进程内的所有线程,相当于调用多个 ExitThread函数和TerminateThread函数。、
线程终止时会进行的操作:
线程拥有的所有用户对象句柄会被释放。
线程退出代码从STILL_ACTIVE变成传给ExitThread或TerminateThread的代码
线程内核对象状态变为触发状态
如果线程时进程最后一个活动线程,系统认为进程也终止了
线程内核对象计数减1
线程内幕:
一调用CreateThread函数,系统会创建一个线程内核对象,该对象的使用计数为2,暂停计数为1,退出代码为STILL_ACTIVE,对象为未触发状态。一旦创建了线程内核对象,系统会分配内存供线程栈使用,此内存是从进程的地址空间分配的,因为线程没有地址空间。系统将两个值传入线程堆栈(线程参数、线程函数)。线程有自己的一组cpu寄存器,称为线程的上下文。上下文反映了当线程上一次执行时,线程的cpu寄存器状态。保存在一个CONTEXT结构中,CONTEXT结构本身保存在内核对象中。指令指针寄存器h和栈指针寄存器是线程上下文中最重要的两个寄存器,线程始终在上下文中运行。CONTEXT结构的堆栈指针寄存器(SP)被设为堆栈中线程函数的地址,而指令指针寄存器被设为RtlUserThreadStart(NTDLL.DLL模块导出)函数的地址。
VOID RtlUserThreadStart(PTHREAD_START_ROUTINE pfnStartAddr, PVOID pvParam) {
__try {
ExitThread( (pfnStartAddr) (pvParam));
}
__except(UnhandledExceptionFilter(GetExceptionInformation () ) ) {
ExitProcess(GetExceptionCode() );
}
}
系统检查CREATE_SUSPENDED标志是否被传给CreateThread函数。没有传递,线程暂停计数递减至0,随后,线程调度给处理器去执行。然后,系统在实际的cpu寄存器中加载上一次在线程上下文中保存的值。