Windows 核心编程 (线程)要点

1、线程的终止(ExitThread和TerminateThread的区别)

 

    ExitThread终止主调线程的运行,并导致操作系统清理该线程使用的所有操作系统资源(线程的堆栈会销毁),但是C/C++资源不会被销毁。

    TerminateThread能杀死任何线程,但除非拥有此线程的进程终止运行,否则系统不会销毁这个线程的堆栈。Microsoft故意以这种方式来实现TerminateThread,让被杀死的线程的堆栈保留在内存中,其它的线程就可以继续正常运行。

 

2、线程终止运行时

   

    线程拥有的所有用户对象句柄会被销毁。在windows中,大多数对象都是由进程所有。但一个线程有两个用户对象:窗口和挂钩。一个线程终止时,系统会自动销毁有线程创建或安装的任何窗口和挂钩。其它的对象只有在进程终止时才被销毁。

 

3、线程内幕

 

    新线程执行RtlUserThreadStart函数的时候,将发生以下事情:

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

    2. 调用线程函数

    3. 线程返回时,RtlUserThreadStart调用ExitThread

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

 

    注意:在RtlUserThreadStart内,线程会调用ExitThread或者ExitProcess,这意味着线程永远不会退出此函数,它始终在内部消亡,所以函数的返回类型为VOID,因为它永远都不会返回。

             同时,因为有了RtlUserThreadStart函数(系统提供的),所以线程函数可以在它的工作之后返回,返回地址保存在堆栈中。但是RtlUserThreadStart函数时不允许返回的,它会运行ExitThread或者ExitProcess,而且堆栈上没有它的返回地址。

 

    一个进程的主线程初始化时,它会调用C/C++运行库的启动代码,后者初始化继而调用你的_tmain或_tWinMain函数。当入口点函数返回时,C/C++运行时启动代码会调用ExitProcess。所以对于C/C++应用程序来说,主线程永远不会返回RtlUserThreadStart函数。

 

4、C/C++运行库

   

    为了保证C/C++多线程应用程序的正常运行,必须创建一个数据结构,并使之与使用了C/C++运行库函数的每个线程关联。然后,在调用了C/C++运行库函数时,那些函数必须知道去查找主调线程的数据块,从而避免影响到其他线程。

    系统在创建新的线程时,是如何知道要分配这个数据库的呢?答案是它并不知道,系统不知道应用程序是用C/C++来写的,不知道你调用的函数并非天生就是线程安全的。保证线程安全是程序员的责任。

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

 5、_beginthreadex函数

    1. 每个线程都有自己的_tiddata内存块,它们是从C/C++运行库的堆上分配的

    2. 传给_beginthreadex的线程函数的地址保存在_tiddata内存块中,参数也保存在这个数据块中

    3. _beginthreadex确实会在内部调用CreateThread

    4. CreateThread调用时,传给它的函数地址是_threadstartex,参数地址是_tiddata结构的地址

    5. 如果一切顺利,返回线程的句柄

    注意:对于errno之类的全局变量,它又是如何工作的呢?实际上,任何时候引用errno,都是在调用内部C/C++运行库函数_errno。_errno函数返回errno变量的地址,而宏errno被定义为获取该地址的内容。

    C/C++运行库还围绕特定的函数放置了同步对象用于malloc等函数在多线程中的同步。

6、用_beginthreadex而不要用CreateThread创建线程

    大多数C/C++运行库函数都是线程安全的,不需要_tiddata结构,但是有些函数却不是线程安全的,需要这个结构。

如果对于一个需要_tiddata结构的线程来说,如果使用CreateThread,首先,C/C++运行库函数尝试取得线程数据块的地址,如果为NULL,表明主调线程没有与之关联的_tiddata块。这个时候,C/C++运行库函数会为主调线程分配并初始化一个_tiddata块。然后这个块就会一直存在并与线程关联。现在C/C++运行库函数可以使用线程的_tiddata块,以后调用的任何C/C++运行库函数都可以使用。

   但是,事实上问题是存在的。第一个问题,如果线程使用了C/C++运行库的signal函数,则整个进程都会终止,因为结构化异常SEH帧没有就绪。第二个问题,如果线程不使用_endthreadex来终止,数据块就不会被销毁,导致内存泄露.

7、绝对不应该使用的C/C++运行库函数

    _beginthread

    _endthread

    _endthread函数在调用ExitThread前,会调用CloseHandle,这也导致了一个很严重的问题(在_beginthread后使用的线程的句柄可能无效)

8、进程或线程伪句柄

    线程引用它的进程内核对象或者它自己的线程内核对象时,

    HANDLE GetCurrentProcess();

    HANDLE GetCurrentThread();

    函数返回的是一个伪句柄。伪句柄不会主调进程句柄表中新建句柄,也不会影响进程内核对象或线程内核对象的使用计数。

    如果调用CloseHandle,将一个伪句柄作为参数输入,CloseHandle只是简单的忽略,并返回FALSE。

  

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

              线程的伪句柄是一个指向当前线程的句柄;换言之,指向的是发出函数调用的那个线程。

              使用DuplicateHandle可以转换

              使用DuplicateHandle会增加内核对象的引用计数.

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值