线程基础

线程有两个部分组成:

1.一个是线程的内核对象,操作系统用它来管理线程。

2.一个线程栈,用于维护线程执行时所需的所有函数参数和局部变量。

假如一个进程上下文中有两个以上的线程运行,这些线程将共享同一个地址空间。这些线程可以共享同样的代码,可以处理相同的数据,此外这些线程号共享内核对象句柄,因为句柄表示针对每个进程的,而不是针对每一个线程。

HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,//SD
SIZE_T dwStackSize,//initialstacksize
LPTHREAD_START_ROUTINE lpStartAddress,//threadfunction
LPVOID lpParameter,//threadargument
DWORD dwCreationFlags,//creationoption
LPDWORD lpThreadId//threadidentifier
)
调用CreateThread时,系统会创建一个线程内核对象。

psa参数指明内核对象的安全属性。传入NULL,表示使用默认安全属性。如果希望子进程能继承到这个对象的句柄,必须指定一个SECURITY_ATTRIBUTES结构,并将该结构的bInherithandle成员初始化为true。表明此内核对象是可继承内核对象。

cbStackSize参数指定线程栈的大小。每个线程都拥有自己的栈。对于主线程cbStackSize使用的是保存在PE文件头的内部的值。可以使用链接器/STACK开关控制这个值,如:
/STACK:[reserve] [,commit]

reserve参数用于设置为线程栈预定多少空间。默认为1MB。commit参数指定最初应为栈预留的地址空间调拨多少物理内存。默认是一个页面。随着线程的执行需要的空间可能不止1MB,如果栈溢出就会产生异常。系统捕获异常并未已预订的空间调拨另一个页面。关于线程栈的内容请参考后面的内容。

当我们在CreateThread中为cbStackSize传入0时,系统会为线程栈预定空间并调拨所有的物理内存。这里只是说会为所有的已预定空间调拨内存,至于预定空间是多大,要么由/STACK链接器开关指定,要么由cStackSize指定。取其中较大的一个。预订的地址空间设定了栈空间的上限,也就是说在栈上限内如果物理内存不够使用还可以调拨,但是一旦达到这个上限就不会再为其分配内存。这可以防止无穷递归。

pfnStartAddr参数指定线程函数的地址,pvParam是传给线程函数的参数。创建多个线程时可以让他们使用同一个函数地址。前面提过使用多线程可能会导致很严重的同步问题,可以采取第八章和第九章的线程同步技术解决。

dwCreateFlags参数控制线程的创建。如果其为0,线程创建之后立即就可以调度。如果值为CREATE_SUSPEND系统将创建并初始化线程,但是会暂停线程的执行,直到调用ResumeThread恢复期运行。

pdwThreadID参数存储线程的ID,它是一个DWORD类型的指针。如果传入NULL,表明我们对线程ID不感兴趣。

线程可以通过以下四种方式终止运行:
1:线程函数返回。
2:线程调用ExitThread杀死自己。
3:其他线程调用TerminateThread。
4:进程终止导致线程终止。

第一种方法是最保险的方式,它可以保证线程的所有资源都会被正确清理。如线程创建的所有C++对象的析构函数都会被调用、线程栈内存被释放、线程的退出代码设为线程函数的返回值。线程内核对象的使用计数被递减。

ExitThread也会导致线程终止运行,同时系统也会清理该线程使用的所有操作系统资源,但是与自然退出线程函数不同,所有线程创建的C++对象的析构函数不会被调用。至于为什么不会被调用待会儿会专门介绍。线程函数正常退出时(对象被析构)会退出到其他函数,在此函数内ExitThread会被调用,此时这是正常的。

TerminateThread可以杀死一个线程,它的第一个参数标识要杀死的线程的句柄,第二个参数为线程退出代码。这个函数不推荐使用。因为它很不负责。被终止的线程在得不到任何通知情况下线程栈不会被销毁。也就相当于把人杀了就把尸体扔那,占用地方。线程栈不会被销毁,还在占用内存,这当然就属于内存泄露。但是这些内存还不能被其他线程访问,如果访问的话会导致访问违规。虽然TerminateThread不会释放线程栈,但是进程结束时栈空间会被清理。   

 线程终止运行时,线程所拥有的所有用户对象句柄会被释放。其退出代码从STILL_ACTIVE变为传给ExitThread或Terminatethread的代码。线程内核对象变为触发状态且引用计数减一。如果该线程是进程的最后一个活动线程,也会导致进程终止。

一旦创建了内核对象,系统就分配内存,供线程的堆栈使用。此内存是从进程的地址空间内分配,因为线程没有自己的地址空间。每个现场都有自己的一组CPU寄存器,成为线程的上下文。上下文反应了当线程上一次执行时,线程的CPU寄存器的状态。线程的CPU寄存器状态全部保存在一个CONTEXT结构,这结构体本身保持在线程内核对象中。指令指针寄存器和栈指针寄存器是线程上下文中最重要的两个寄存器,线程永远在进程的上下文中运行。所以这两个地址标识的内存都位于线程所在进程的地址空间中。

线程完全初始化之后,系统将检查CREATE_SUSPEND标志是否已被传入CreateThread函数。如果此标识没有传递,系统将线程的挂起计数递减至0,随后线程就可以调度给一个处理器去执行。

新县城执行RtlUserThreadStart函数的时候,将发生一下事情:

1.围绕线程函数,会设置一个结构化异常处理(SEH),这样一来,线程执行期间所产生的任何异常都能够得到系统的默认处理

2.系统调用线程函数,吧传递给CreateThread函数的pvParam参数传给他

3.线程函数返回时,RtlUserThreadStart调用ExitThread,将你的线程函数的返回值传给他

4.如果线程产生了一个未被处理的异常,RtlUserThreadStart函数所设置的SEH帧会处理这异常。通常这意味着系统会向用户显示一个消息框,而且当用户关闭此消息框时,RtlUserThreadStart会调用ExitProcess来终止整个进程。

vs附带的c/c++库

libcmt.lib:库的静态链接发行版本

libcmtd.lib:库的静态链接调试版本

msvcrt.lib:导入库,用于动态链接msvcr80.dll库的发现版本(这是新建项目的默认库)

msvcrtd.lib:导入库,用于动态链接msvcr80d.dll库的调试版本

msvcmrt.lib:导入库,用于托管/本机代码混合

msvcurt.lib:导入库,编译成百分百的纯msil代码

创建线程的时候,一定不要调用操作系统的CreateThread函数,相反必须调用c/c++运行库函数_beginthreadex。

_beginthreadex和CreateThread函数的参数列表是一样的,但是参数名称和类型并不完全一样。

对于_beginThreadex函数,需要关注一下几点

1.每个线程都有自己的专用_tiddata内存块,他们是c/c++运行库的堆上分配的

2.传给_beginThreadex的线程函数的地址保持在_tiddata内存块中。

3._beginThreadex确实会在内部调用createThread,因为操作系统只知道用这种方式来创建一个新线程

4.CreateThread函数被调用时,传给他的函数地址是_threadstartex(而非pfnStartAddr),另外,参数地址是_tiddata结构的地址,而非pvParam.

5.如果一切顺利,会返回线程的句柄,任何操作失败,返回0.


对于_endThreadex函数,要注意以下几点

1.C运行库的_getPtd_noexit函数在内部调用操作系统的Tlsgetvalue函数,后者获取主调线程的_tiddata内存块的地址

2.然后_endThreadex将此数据块释放,并调用操作系统的exitThread函数来实际的销毁线程,它会传递并正确的设置退出代码。

新的_beginthreadex和_endthreadex函数已经取代了这两个传统的函数(_beginthread和_endthread)。

handle GetCurrentProcess()和handle getCurrentThread()这两个函数都返回主调线程的进程内核对象和线程内核对象的一个伪句柄。

dword GetCurrentProcessId()和dword getCurrentThreadid()这两个函数都返回主调线程的进程内核对象和线程内核对象的ID。

将伪句柄转换为真正的句柄:

HANDLE hProcess=NULL;  
DuplicateHandle(GetCurrentProcess(),  
    GetCurrentProcess(),  
    GetCurrentProcess(),  
    &hProcess,  
    0,  
    FALSE,  
    DUPLICATE_SAME_ACCESS);//伪进程句柄转为真实进程句柄  
//...  
//不用的时候必须关闭 不然会有资源泄露  
CloseHandle(hProcess);  
HANDLE hThread=NULL;  
DuplicateHandle(GetCurrentProcess(),  
    GetCurrentThread(),  
    GetCurrentProcess(),  
    &hThread,  
    0,  
    FALSE,  
    DUPLICATE_SAME_ACCESS);//伪线程句柄转换为真实线程句柄  
//..    
//不用时也必须关闭  
CloseHandle(hThread);  






  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值