总结CreateThread与_beginthreadex的区别

  1. 一  为什么不直接使用CreateThread而要使用运行库函数_beginthreadex函数
  2. 二  _beginthreadex函数都干了啥
  3. 三  _threadstartex函数做了什么工作
  4. 四  为什么不应该调用ExitThread函数
  5. 五  不应该使用_beginthread_endthread函数不带ex的两个旧函数
  6. 对一1中例外的说明

看了MoreWindows同学的文章秒杀多线程第二篇 多线程第一次亲密接触 CreateThread与_beginthreadex本质区别,顺便又翻了一下《windows核心编程》,发现以前看过一遍的东西都忘记了。为了加深印象,所以把一些重要点记录下来。


以下内容读者都可以在《windows核心编程》第6章 线程基础中找到。


一  为什么不直接使用CreateThread,而要使用运行库函数_beginthreadex函数?

1  CreateThread不会在创建线程之前申请一个_tiddata结构内存块,当一个线程调用一个需要_tiddata结构的C/C++运行库函数(例如errno, strtok, _gmtime等)时,虽然C/C++运行库函数会为主调函数申请并初始化一个_tiddata块,但是在线程退出的时候,如果不是调用_endthreadex函数的话,_tiddata块将不能被释放,从而导致内存泄露。(这里会有一个例外,后面说)

2  如果线程使用了C/C++运行库的signal函数(该函数用于设置中断信号处理函数,比如可以使用此函数屏蔽CTRL+C,更多介绍可参阅MSDN),则整个进程会异常退出。因为CreateThread创建的线程其结构化异常处理帧(SEH)没有设置,导致程序不能处理异常。


二  _beginthreadex函数都干了啥?

1  对照前面第一点中提到的CreateThread的隐患,_beginthreadex都做了相应的处理,首先_beginthreadex函数会申请_tiddata数据结构块,然后会将要创建线程的入口点和参数都设置到该_tiddata数据结构中

2  _beginthreadex内部确实调用了CreateThread这一系统API,因为这是操作系统创建新线程的唯一方法。

但是调用CreateThread时,所给的新线程入口点是_threadstartex,而不是外部由程序员提供的线程入口地址,同时,传递给CreateThread的参数也不是外部由程序员提供的参数,而是_threadstartex申请的_tiddata数据结构块。

所以,接下来的工作都将转入到_threadstartex函数中。


三  _threadstartex函数做了什么工作?

1  首先使用TlsSetVlue函数将_tiddata结构和线程关联起来。

2  初始化浮点操作支持。

3  设置SEH链,捕捉C++异常,例如在第一点中提到的C/C++运行库的signal函数,需要异常处理支持。

4  以上工作就绪后,会调用保存在_tiddata中由外部程序员提供的线程入口函数,并传入保存在_tiddata中的参数,这样就启动了线程代码。

5  该函数将一直等待线程的结束,当线程返回时,调用_endthreadex函数,在_endthreadex内部通过_getptd和_freeptd释放掉_tiddata结构的内存。_endthreadex函数最终会调用系统的ExitThread API(这时候调用该函数已经是安全的了,因为_tiddata内存已经释放)。


四  为什么不应该调用ExitThread函数?

这一点在第三点中已经说明了,直接调用ExitThread 函数会导致_tiddata内存无法释放,如果不得已,外部必须要结束一个线程,可以调用_endthreadex来结束线程,_endthreadex会释放_tiddata内存块。


五  不应该使用_beginthread、_endthread函数(不带ex的两个旧函数)。

具体原因参阅《windows核心编程》。


对一.1中例外的说明:

根据《windows核心编程》中的说明,当模块链接到C/C++运行库的DLL版本时,这个库(例如VS2008的msvcr90d.dll,msvcr90.dll)会在线程终止时收到一个DLL_THREAD_DETACH通知,然后会执行释放_tiddata块(如果有申请)。虽然这是可以防止_tiddata块内存泄露的,但仍然强烈建议使用_beginthreadex来创建线程,而不要使用CreateThread。因为不能保证所有使用者都使用动态链接库版本。

对于msvcr90d.dll在线程销毁时所做的工作,我用反汇编工具IDA加载看了一下,关键代码如下:

text:1023C889 loc_1023C889:                           ; CODE XREF: sub_1023C610+1CAj
.text:1023C889                 cmp     [ebp+arg_4], 3  ; 这里比较是不是DLL_THREAD_DETACH
.text:1023C88D                 jnz     short loc_1023C899
.text:1023C88F                 push    0
.text:1023C891                 call    __freeptd       ; 释放_tiddata结构,及结构中申请的内存
.text:1023C896                 add     esp, 4

__freeptd函数中调用了 _freefls函数,该函数会释放_tiddata内存块。


==================全文完==================

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值