揭祕!爲什麼要用_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

相關文章
相關標籤/搜索