天天看點

CreateThread、_beginthreadex和AfxBeginThread 的差別

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函數是用來建立線程的Windows函數不過,如果你正在編寫C/C++代碼,決不應該調用CreateThread。相反,應該使用Visual C++運作期庫函數_beginthreadex。如果不使用Microsoft的Visual C++編譯器,你的編譯器供應商有它自己的CreateThred替代函數。不管這個替代函數是什麼,你都必須使用。”

"_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     

這樣的提示!

繼續閱讀