CreateThread、_beginthreadex和AfxBeginThread的區別

double2li發表於2014-04-30

CreateThread、_beginthreadex和AfxBeginThread

建立執行緒好幾個函式可以使用,可是它們有什麼區別,適用於什麼情況呢?
參考了一些資料,寫得都挺好的,這裡做一些摘抄和整合。

【參考1】CreateThread, AfxBeginThread,_beginthread, _beginthreadex的區別 =====================================================================

1、CreateThread——Windows的API函式
2、_beginthreadex——MS對C Runtime庫的擴充套件SDK函式
3、AfxBeginThread——MFC中執行緒建立的MFC函式

CreateThread
(API函式:SDK函式的標準形式,直截了當的建立方式,任何場合都可以使用。)
提供作業系統級別的建立執行緒的操作,且僅限於工作者執行緒。不呼叫MFC和RTL的函式時,可以用CreateThread,其它情況不要輕易。在使用的過程中要考慮到程式的同步與互斥的關係(防止死鎖)。
執行緒函式定義為:DWORD WINAPI _yourThreadFun(LPVOID pParameter)。
但它沒有考慮:
(1)C Runtime中需要對多執行緒進行紀錄和初始化,以保證C函式庫工作正常(典型的例子是strtok函式)。
(2)MFC也需要知道新執行緒的建立,也需要做一些初始化工作(當然,如果沒用MFC就沒事了)。 

_beginthreadex
MS對C Runtime庫的擴充套件SDK函式,首先針對C Runtime庫做了一些初始化的工作,以保證C Runtime庫工作正常。然後,呼叫CreateThread真正建立執行緒。 僅使用Runtime Library時,可以用_BegingThread。

AfxBeginThread
MFC中執行緒建立的MFC函式,首先建立了相應的CWinThread物件,然後呼叫CWinThread::CreateThread,   在CWinThread::CreateThread中,完成了對執行緒物件的初始化工作,然後,呼叫_beginthreadex(AfxBeginThread相比較更為安全)建立執行緒。它簡化了操作或讓執行緒能夠響應訊息,即可用於介面執行緒,也可以用於工作者執行緒,但要注意不要在一個MFC程式中使用_beginthreadex()或CreateThread()。
執行緒函式定義為:UINT _yourThreadFun(LPVOID pParam)

=====================================================================

【參考2】CreateThread與_beginthreadex
=====================================================================

CreateThread
“CreateThread函式是用來建立執行緒的Windows函式不過,如果你正在編寫C/C++程式碼,決不應該呼叫CreateThread。相反,應該使用Visual C++執行期庫函式_beginthreadex。如果不使用Microsoft的Visual C++編譯器,你的編譯器供應商有它自己的CreateThred替代函式。不管這個替代函式是什麼,你都必須使用。”

_beginthreadex
“_beginthreadex函式的引數列表與CreateThread函式的引數列表是相同的,但是引數名和型別並不完全相同。這是因為 Microsoft的C/C++執行期庫的開發小組認為, C/C++執行期函式不應該對Windows資料型別有任何依賴。_beginthreadex函式也像CreateThread那樣,返回新建立的執行緒的控制程式碼。因此,如果呼叫原始碼中的CreateThread,就很容易用對_beginthreadex的呼叫全域性取代所有這些呼叫。不過,由於資料型別並不完全相同,所以必須進行某種轉換,使編譯器執行得順利些。” 
“下面是關於_beginthreadex的一些要點: 
1) 每個執行緒均獲得由C/C++執行期庫的堆疊分配的自己的tiddata記憶體結構。(tiddata結構位於Mtdll.h檔案中的Visual C++原始碼中)。 
2) 傳遞給_beginthreadex的執行緒函式的地址儲存在tiddata記憶體塊中。傳遞給該函式的引數也儲存在該資料塊中。 
3) _beginthreadex確實從內部呼叫CreateThread,因為這是作業系統瞭解如何建立新執行緒的唯一方法。 
4) 當呼叫CreatetThread時,它被告知通過呼叫_threadstartex而不是pfnStartAddr來啟動執行新執行緒。還有,傳遞給執行緒函式的引數是tiddata結構而不是pvParam的地址。 
5) 如果一切順利,就會像CreateThread那樣返回執行緒控制程式碼。如果任何操作失敗了,便返回NULL。”

為什麼?
  “也許你想知道,如果呼叫CreateThread,而不是呼叫C/C++執行期庫的_beginthreadex來建立新執行緒,將會發生什麼情況。當一個執行緒呼叫要求tiddata結構的C/C++執行期庫函式時,將會發生下面的一些情況(大多數C/C++執行期庫函式都是執行緒安全函式,不需要該結構)。
  首先,C/C++執行期庫函式試圖(通過呼叫TlsGetValue)獲取執行緒的資料塊的地址。如果返回NULL作為tiddata塊的地址,呼叫執行緒就不擁有與該地址相關的tiddata塊。這時,C/C++執行期庫函式就在現場為呼叫執行緒分配一個tiddata塊,並對它進行初始化。然後該 tiddata塊(通過TlsSetValue)與執行緒相關聯。此時,只要執行緒在執行,該tiddata將與執行緒待在一起。這時,C/C++執行期庫函式就可以使用執行緒的tiddata塊,而且將來被呼叫的所有C/C++執行期函式也能使用tiddata塊。 
  當然,這看來有些奇怪,因為執行緒執行時幾乎沒有任何障礙。不過,實際上還是存在一些問題。首先,如果執行緒使用C/C++執行期庫的signal函式,那麼整個程式就會終止執行,因為結構化異常處理幀尚未準備好。第二,如果不是呼叫_endthreadex來終止執行緒的執行,那麼資料塊就不會被撤消,記憶體洩漏就會出現(那麼誰還為使用CreateThread函式建立的執行緒來呼叫_endthreadex呢?)。 
   注意如果程式模組連結到多執行緒DLL版本的C/C++執行期庫,那麼當執行緒終止執行並釋放tiddata塊(如果已經分配了tiddata塊的話)時,該執行期庫會收到一個DLL_THREAD_DETACH通知。儘管這可以防止tiddata塊的洩漏,但是強烈建議使用_beginthreadex而不是使用Createthread來建立執行緒。

=====================================================================

【參考3】關於_beginthreadex和CreateThread的區別
=====================================================================

  在 Win32 API 中,建立執行緒的基本函式是 CreateThread,而 _beginthread(ex) 是C++ 執行庫的函式。為什麼要有兩個呢?因為C++ 執行庫裡面有一些函式使用了全域性量,如果使用 CreateThread 的情況下使用這些C++ 執行庫的函式,就會出現不安全的問題。而 _beginthreadex 為這些全域性變數做了處理,使得每個執行緒都有一份獨立的“全域性”量。
  所以,如果你的程式設計只呼叫 Win32 API/SDK ,就放心用 CreateThread;如果要用到C++ 執行時間庫,那麼就要使用 _beginthreadex ,並且需要在編譯環境中選擇 Use MultiThread Lib/DLL。
  通常他們的解釋都是這容易造成記憶體洩漏。這個解釋本身是沒有錯的,但是解釋得不夠完全和詳細。以至於造成很多新手盲目的信任了那句話,在那裡都是用_beginthreadex函式,或者是裝作沒有看到使用CreateThread函式。曾經有一段時間我也對這個問題很是困惑,不知道到底用那個才是對的。因為我不止一次在很多權威性的程式碼中看到對CreateThread函式的直接呼叫。難道是權威錯了?? 抱著懷疑的態度查詢了大量的資料和書籍,終於搞明白了這個問題的關鍵所在,在此做個說明,算是對那句話的一個完善。
  關於_beginthreadex和CreateThread的區別我就不做說明了,這個很容易找到的。我們只要知道一個問題:_beginthreadex是一個C執行時庫的函式,CreateThread是一個系統API函式,_beginthreadex內部呼叫了CreateThread。只所以所有的書都強調記憶體洩漏的問題是因為_beginthreadex函式在建立執行緒的時候分配了一個堆結構並和執行緒本身關聯起來,我們把這個結構叫做tiddata結構,是通過執行緒本地儲存器TLS於執行緒本身關聯起來。我們傳入的執行緒入口函式就儲存在這個結構中。tiddata的作用除了儲存執行緒函式入口地址之外,還有一個重要的作用就是:C執行時庫中有些函式需要通過這個結構來儲存和獲取一些資料,比如說errno之類的執行緒全域性變數。這點才是最重要的。
  當一個執行緒呼叫一個要求tiddata結構的執行時庫函式的時候,將發生下面的情況:
  執行時庫函式試圖TlsGetv alue獲取執行緒資料塊的地址,如果沒有獲取到,函式就會現場分配一個 tiddata結構,並且和執行緒相關聯,於是問題出現了,如果不通過_endthreadex函式來終結執行緒的話,這個結構將不會被撤銷,記憶體洩漏就會出現了。但通常情況下,我們都不推薦使用_endthreadex函式來結束執行緒,因為裡面包含了ExitThread呼叫。
  找到了記憶體洩漏的具體原因,我們可以這樣說:只要在建立的執行緒裡面不使用一些要求tiddata結構的執行時庫函式,我們的記憶體時安全的。所以,前面說的那句話應該這樣說才完善:
  “絕對不要呼叫系統自帶的CreateThread函式建立新的執行緒,而應該使用_beginthreadex,除非你線上程中絕不使用需要tiddata結構的執行時庫函式”
  這個需要tiddata結構的函式有點麻煩了,在侯捷的《win32多執行緒程式設計》一書中這樣說到:
   如果在除主執行緒之外的任何執行緒中進行一下操作,你就應該使用多執行緒版本的C runtime library,並使用_beginthreadex和_endthreadex:
   1 使用malloc()和free(),或是new和delete
   2 使用stdio.h或io.h裡面宣告的任何函式
   3 使用浮點變數或浮點運算函式
   4 呼叫任何一個使用了靜態緩衝區的runtime函式,比如:asctime(),strtok()或rand()

=====================================================================

【參考4】_beginthreadex、CreateThread、AfxBeginThread的選擇=====================================================================

1. Create/EndThread是Win32方法開始/結束一個執行緒
_beginthreadx/_endthreadex是C RunTime方式開始/結束一個執行緒
AfxBeginThread在MFC中開始/結束一個執行緒

2.直接在CreateThread API建立的執行緒中使用sprintf,malloc,strcat等涉及CRT儲存堆操作的CRT庫函式是很危險的,容易造成執行緒的意外中止。 在使用_beginthread和_beginthreadex建立的執行緒中可以安全的使用CRT函式。但是必須線上程結束的時候相應的呼叫_endthread或_endthreadex

3._beginthread成對呼叫的_endthread函式內部隱式的呼叫CloseHandle關閉了執行緒控制程式碼,而與_beginthreadex成對使用的_endthreadex則沒有關閉執行緒的控制程式碼,需要顯示的呼叫CloseHandle關閉執行緒控制程式碼,不要使用_beginthread,使用._beginthreadex代替之。

4.儘量不要在一個MFC程式中使用_beginthreadex()或CreateThread()。

5.沒有使用到MFC的執行緒儘量用_beginthreadex啟動。

6.如果在一個與LIBCMT.LIB連結的程式中呼叫C Runtime函式,則必須要用_beginthreadex啟動執行緒

7._beginthreadex啟動的執行緒可以安全的呼叫任何C Runtime 函式

=====================================================================

【參考5】CreateThread()、_beginthread()以及_beginthreadex()聯絡與區別
=====================================================================

<<Windows核心程式設計>>中有很詳細地介紹:
注意:若要建立一個新執行緒,絕對不要使用CreateThread,而應使用_beginthreadex.
Why?考慮標準C執行時庫的一些變數和函式,如errno,這是一個全域性變數。全域性變數用於多執行緒會出什麼事,你一定知道的了。故必須存在一種機制,使得每個執行緒能夠引用它自己的errno變數,又不觸及另一執行緒的errno變數._beginthreadex就為每個執行緒分配自己的tiddata記憶體結構。該結構儲存了許多像errno這樣的變數和函式的值、地址(自己看去吧)。
通過執行緒區域性儲存將tiddata與執行緒聯絡起來。具體實現在Threadex.c中有。   
結束執行緒使用函式_endthreadex函式,釋放掉執行緒的tiddata資料塊。   
CRT的函式庫線上程出現之前就已經存在,所以原有的CRT不能真正支援執行緒,這導致我們在程式設計的時候有了CRT庫的選擇,在MSDN中查閱CRT的函式時都有:
  Libraries   
  LIBC.LIB   Single   thread   static   library,   retail   version     
  LIBCMT.LIB   Multithread   static   library,   retail   version     
  MSVCRT.LIB   Import   library   for   MSVCRT.DLL,   retail   version     
這樣的提示!

=====================================================================


相關文章