揭秘!为什么要用_beginthreadex,而不用CreateThread和_beginthread

  因为历史缘由,因此C/C++运行库并非为多线程应用程序而设计的,因此为了保证其中的某些变量和函数的安全,那么必须建立一个数据结构,并使之与使用了C/C++运行库函数的每一个线程所关联。当在调用C/C++运行库函数时,那些函数必须读取主调本身的线程的数据块,从而避免印象其余线程。c++

       因此当编写C/C++代码时,请调用_beginthreadex,而不要使用CreateThread的大体缘由就是如上所述。详细内容请继续往下读安全

       _beginthreadex的函数参数列表与CreateThread同样,可是参数名和类型有所不一样。这是由于C/C++程序不该该对Windows系统的类型有任何的依赖。数据结构

       _beginthreadex也会返回新建线程的句柄,就像CreateThread同样。能够很是方便的替换本身程序中的CreateThread函数,可是数据类型不同,因此还得添加一些类型转换便可。多线程

       透过_beginthreadex函数的源码咱们可知,函数

        1.每一个线程都有本身专用的_tiddata内存块,它们是从C/C++运行库的堆上分配的。编码

        2.传给_beginthreadex的线程函数地址(pfnStartAddr)保存在_tiddata内存块中。(_tiddata结构能够在Mtdll.h源码中窥探一二)操作系统

        3._beginthreadex会在内部调用CreateThread。.net

       4.调用CreateThread时传入的地址是_threadstartex(而非传入的pfnStartAddr)。参数地址是_tiddata结构地址,而非pvParam。线程

        5.若是一切顺利,会返回线程的句柄,操做失败会返回0。设计

      初始化而且传入_tiddata后,又怎么将其和线程关联呢?继续往下看

      对于_threadstartex函数及其辅助函数__callthreadstartex:

        1.新的线程首先执行RtlUserThreadStart,而后在跳转到_threadstartex。(这便于上一篇所讲的"建立线程的内幕"一文中的知识点所契合)

        2._threadstartex惟一的参数就是新线程的_tiddata内存块地址。

      3.TlsSetValue是一个操做系统函数,做用是将一个值与主调函数关联起来。(这就是所谓的线程局部存储TLS)_threadstartex函数将_tiddata内存块与新建线程关联起来。------华丽的分割线--------

        4.无参数的辅助函数__callthreadstartex中,有一个SEH帧,它处理着许多与运行库有关的事情——好比运行时错误(如抛出未被捕获的C++异常)——和c/c++运行库的signal函数。这点很重要,由于用CreateThread建立的线程,调用C/C++运行库的signal函数,其是不能正常工做的。

       5.预期要执行的线程函数会被调用,并向其传递预期的参数。(线程的地址和参数都是存在于TLS中保存的_tiddata数据块中,并会在__callthreadstartex中在TLS中获取获得)6.线程函数的返回值被认为是线程的退出代码。注意:函数不是返回_threadstartex而后返回RltUserThreadStart(由于这样_tiddata内存块不会被释放),而是调用_endthreadex,并向其传入线程函数的返回值。

对于_endthreadex,其内部先获取TLS中的_tiddata块,而后释放掉,并调用操做系统的ExitThread函数来实际销毁线程,并传递正确的退出码。

        总结:因此如今应该理解为何不要调用CreateThread了,由于调用CreateThread后,在线程调用一个须要_tiddata块的c/c++运行库函数时(主要经过TlsGetValue),会获得NULL,这时c/c++运行库会为其初始化一个块,而后这个块与线程关联(主要经过TlsSetValue)。这以后,任何使用_tiddata块的函数都能正常使用了。可是,问题仍是有的:1.若是线程使用了signal函数,则整个进程会终止掉(上文有详细描述,关于__callthreadstartex的SEH帧没有准备就绪)。2.线程入口点函数返回后,会在RtlUserThreadStart中直接调用ExitThread,其并无释放掉_tiddata内存块,这会引发内存泄漏。

        附加:毫不调用_beginthread。由于,1._beginthread参数较少,不能建立具备安全属性的线程,不能让线程当即挂起等。_endthread也是如此,其退出代码被硬编码为0。

_endthread还存在一个问题,就是在调用ExitThread前,会调用CloseHandle,向其传入新线程的句柄。举个例子,好比你使用了_beginthread建立了一个新的线程,在其以后使用CloseHandle关闭线程句柄。因为_beginthread会调用_endthread函数终止线程,而线程内核对象的初始化计数为2,在_endthread函数中,调用ExitThread前,会调用CloseHandle,也就是说会连续两次递减引用计数,这时引用计数为0,内核对象被系统销毁,再以后在调用CloseHandle关闭线程句柄时,这个传入的句柄是无效的,即CloseHandle函数的调用会以失败了结。因此请用_beginthreadex代替_beginthread,_endthreadex代替_endthread。      _beginthreadex调用的是_endthreadex,_beginthread调用的是_endthread

相关文章
相关标签/搜索