[英]Windows threading: _beginthread vs _beginthreadex vs CreateThread C++
有什么更好的方式啟動_beginthread
, _beginthreadx
或CreateThread
線程?
我正在嘗試確定_beginthread
, _beginthreadex
和CreateThread
的優點/缺點是什么。 所有這些函數都將線程句柄返回到新創建的線程,我已經知道,當發生錯誤時(可以通過調用GetLastError
進行檢查),CreateThread提供了一些額外的信息...但是當我進行操作時,我應該考慮哪些事項?正在使用這些功能?
我正在使用Windows應用程序,因此跨平台兼容性已經不是問題。
我已經閱讀了msdn文檔,例如,我什至不明白為什么有人會決定使用_beginthread而不是CreateThread,反之亦然。
干杯!
更新:好的,謝謝您提供的所有信息,如果在使用_beginthread()
,我在一些地方也無法調用WaitForSingleObject()
_beginthread()
,但是如果我在線程中調用_endthread()
則不應該這樣工作? 那有什么事
CreateThread()
是Win32 API的原始調用,用於在內核級別創建另一個控制線程。
_beginthread()
和_beginthreadex()
是C運行時庫調用,它們在后台調用CreateThread()
。 一旦CreateThread()
返回, _beginthread/ex()
會進行額外的記賬,以使C運行時庫在新線程中可用並保持一致。
在C ++中,幾乎可以肯定使用_beginthreadex()
除非您根本不會鏈接到C運行時庫(aka MSVCRT * .dll / .lib)。
_beginthread()
和_beginthreadex()
之間有一些區別。 _beginthreadex()
行為更像CreateThread()
(在兩個參數及其行為方面)。
如Drew Hall所述 ,如果您使用的是C / C ++運行時,則必須使用_beginthread()
/ _beginthreadex()
而不是CreateThread()
以便運行時有機會執行自己的線程初始化(設置線程本地存儲)等)。
實際上,這意味着您的代碼幾乎絕不應直接使用CreateThread()
。
_beginthread()
/ _beginthreadex()
的MSDN文檔中有很多區別方面的詳細信息-更重要的一點是,由於_beginthread()
創建的線程的線程句柄在線程退出時由CRT自動關閉,“如果_beginthread生成的線程快速退出,則返回給_beginthread調用者的句柄可能無效,或者更糟糕的是,指向另一個線程”。
這是CRT源代碼中對_beginthreadex()
的注釋:
Differences between _beginthread/_endthread and the "ex" versions:
1) _beginthreadex takes the 3 extra parameters to CreateThread
which are lacking in _beginthread():
A) security descriptor for the new thread
B) initial thread state (running/asleep)
C) pointer to return ID of newly created thread
2) The routine passed to _beginthread() must be __cdecl and has
no return code, but the routine passed to _beginthreadex()
must be __stdcall and returns a thread exit code. _endthread
likewise takes no parameter and calls ExitThread() with a
parameter of zero, but _endthreadex() takes a parameter as
thread exit code.
3) _endthread implicitly closes the handle to the thread, but
_endthreadex does not!
4) _beginthread returns -1 for failure, _beginthreadex returns
0 for failure (just like CreateThread).
2013年1月更新 :
VS 2012的CRT在_beginthreadex()
執行了一些初始化操作:如果進程是“打包的應用程序”(如果從GetCurrentPackageId()
返回了有用的東西),則運行時將在新創建的線程上初始化MTA。
通常,正確的做法是調用_beginthread()/_endthread()
(或ex()
變體)。 但是,如果將CRT用作.dll,則CRT的狀態將被正確初始化和銷毀,因為分別調用CreateThread()
和ExitThread()
或返回時,將使用DLL_THREAD_ATTACH
和DLL_THREAD_DETACH
調用CRT的DllMain
。
可以在VC的安裝目錄VC \\ crt \\ src \\ crtlib.c下找到CRT的DllMain
代碼。
這是_beginthreadex
核心的代碼(請參見crt\\src\\threadex.c
):
/*
* Create the new thread using the parameters supplied by the caller.
*/
if ( (thdl = (uintptr_t)
CreateThread( (LPSECURITY_ATTRIBUTES)security,
stacksize,
_threadstartex,
(LPVOID)ptd,
createflag,
(LPDWORD)thrdaddr))
== (uintptr_t)0 )
{
err = GetLastError();
goto error_return;
}
_beginthreadex
的其余部分為CRT初始化每個線程的數據結構。
使用_beginthread*
的優點是,您從線程進行的CRT調用將正常工作。
您應該使用_beginthread
或_beginthreadex
來允許C運行時庫執行它自己的線程初始化。 只有C / C ++程序員才需要知道這一點,因為他們現在應該了解使用自己的開發環境的規則。
如果使用_beginthread
,則不需要調用CloseHandle
因為RTL會為您服務。 這就是為什么使用_beginthread
不能等待句柄的原因。 如果線程函數立即(迅速)退出(因為啟動線程可能對剛剛啟動的線程持有無效的線程句柄)而立即退出,則_beginthread
也會引起混亂。
_beginthreadex
句柄可以用於等待,但也需要顯式調用CloseHandle
。 這是使它們在等待時可以安全使用的部分原因。 使其完全安全的另一個問題是始終將線程掛起。 檢查是否成功,記錄句柄等。恢復線程。 為了防止線程在啟動線程可以記錄其句柄之前終止,這是必需的。
最佳實踐是使用_beginthreadex
,先開始掛起,然后在記錄句柄之后恢復,等待句柄確定,必須調用CloseHandle
。
當您在代碼中使用任何CRT函數時, CreateThread()
曾經會導致內存泄漏 。 _beginthreadex()
與CreateThread()
_beginthreadex()
具有相同的參數,並且比_beginthread()
更通用。 因此,我建議您使用_beginthreadex()
。
關於您更新的問題:“如果使用_beginthread()
,我也讀過幾個地方無法調用WaitForSingleObject()
_beginthread()
,但是如果我在線程中調用_endthread()
,那行不通嗎?”
通常,可以將線程句柄傳遞給WaitForSingleObject()
(或其他等待對象句柄的API)以阻塞直到線程完成。 但是,在調用_endthread()
時, _beginthread()
創建的線程句柄是關閉的(可以顯式完成,也可以在線程過程返回時在運行時隱式完成)。
該問題在WaitForSingleObject()
的文檔中指出:
如果在等待仍未完成的情況下關閉了此句柄,則該函數的行為是不確定的。
查看函數簽名, CreateThread
與_beginthreadex
幾乎相同。
_beginthread
, _beginthreadx
與CreateThread
HANDLE WINAPI CreateThread(
__in_opt LPSECURITY_ATTRIBUTES lpThreadAttributes,
__in SIZE_T dwStackSize,
__in LPTHREAD_START_ROUTINE lpStartAddress,
__in_opt LPVOID lpParameter,
__in DWORD dwCreationFlags,
__out_opt LPDWORD lpThreadId
);
uintptr_t _beginthread(
void( *start_address )( void * ),
unsigned stack_size,
void *arglist
);
uintptr_t _beginthreadex(
void *security,
unsigned stack_size,
unsigned ( *start_address )( void * ),
void *arglist,
unsigned initflag,
unsigned *thrdaddr
);
此處的說明說_beginthread
可以使用__cdecl
或__clrcall
調用約定作為起點,而_beginthreadex
可以使用__stdcall
或__clrcall
作為起點。
我認為人們對CreateThread
中的內存泄漏所做的任何評論都已經有十多年的歷史了,應該予以忽略。
有趣的是,這兩個_beginthread*
函數實際上是在我的計算機上的C:\\Program Files (x86)\\Microsoft Visual Studio 10.0\\VC\\crt\\src
實際上調用了CreateThread
。
// From ~line 180 of beginthreadex.c
/*
* Create the new thread using the parameters supplied by the caller.
*/
if ( (thdl = (uintptr_t)
CreateThread( (LPSECURITY_ATTRIBUTES)security,
stacksize,
_threadstartex,
(LPVOID)ptd,
createflag,
(LPDWORD)thrdaddr))
== (uintptr_t)0 )
{
err = GetLastError();
goto error_return;
}
beginthreadex
給你一個線程HANDLE
在使用WaitForSingleObject
朋友。 beginthread
沒有。 完成后不要忘記使用CloseHandle()
。 真正的答案是使用boost::thread
或C ++ 09的線程類。
CreateThread()
是Windows API調用,與語言無關。 它只是創建OS對象-線程,並向該線程返回HANDLE。 所有Windows應用程序都使用此調用來創建線程。 出於明顯的原因,所有語言都避免直接調用API:1.您不希望代碼特定於OS。2.在調用類似API之前,您需要做一些整理工作:轉換參數和結果,分配臨時存儲空間等。
_beginthreadex()
是CreateThread()
C包裝程序,它專用於C。 通過分配線程特定的存儲,它使原始的單線程C f-ns在多線程環境中工作。
如果不使用CRT,則無法避免直接調用CreateThread()
。 如果使用CRT,則必須使用_beginthreadex()
否則某些CRT字符串f-ns在VC2005之前可能無法正常工作。
CreateThread()
曾經是一個CreateThread()
因為CRT將被錯誤地初始化/清理。 但這現在已經成為歷史:現在(可以使用VS2010和可能的幾個版本)可以調用CreateThread()
而不必破壞CRT。
這是官方的MS確認 。 它指出一個例外:
實際上,用
CreateThread()
創建的線程中不應該使用的唯一函數是signal()
函數。
但是,從一致性的角度來看,我個人更喜歡繼續使用_beginthreadex()
。
與_beginthread
相比,使用_beginthreadex
可以:
OpenThread
一起使用的線程ID。 CloseHandle
關閉手柄。 _beginthreadex
非常類似於CreateThread
,但是前者是CRT實現,后者是Windows API調用。 CreateThread的文檔包含以下建議:
可執行文件中的調用C運行時庫(CRT)的線程應使用
_beginthreadex
和_endthreadex
函數進行線程管理,而不是CreateThread
和ExitThread
; 這需要使用CRT的多線程版本。 如果使用CreateThread
創建的線程調用CRT,則CRT可能會在內存不足的情況下終止進程。
CreateThread()
是直接系統調用。 它是在Kernel32.dll
上實現的,很可能您的應用程序已因其他原因被鏈接到該文件。 它在現代Windows系統中始終可用。
_beginthread()
和_beginthreadex()
是Microsoft C運行時( msvcrt.dll
)中的包裝函數。 文檔中說明了兩次調用之間的區別。 因此,當Microsoft C運行時可用,或者您的應用程序與之靜態鏈接時,它就可用。 除非您使用純Windows API進行編碼(就像我個人經常做的那樣),否則您也可能會鏈接到該庫。
您的問題是一個連貫的問題,實際上是一個反復出現的問題。 與許多API一樣,我們必須處理Windows API中存在重復且模棱兩可的功能。 最糟糕的是,文檔並未闡明問題。 我想創建_beginthread()
函數家族是為了更好地與其他標准C功能集成,例如errno
的操縱。 _beginthread()
因此可以更好地與C運行時集成。
盡管如此,除非您有充分的理由使用_beginthread()
或_beginthreadex()
,否則應使用CreateThread()
,主要是因為在最終的可執行文件中,庫依賴關系可能會減少一些(對於MS CRT,這確實有點問題)。 您也沒有環繞該調用的包裝代碼,盡管這種影響可以忽略不計。 換句話說,我相信堅持使用CreateThread()
主要原因是沒有充分的理由使用_beginthreadex()
開頭。 功能完全或幾乎相同。
使用_beginthread()
一個好理由是 (似乎是錯誤的),如果調用_endthread()
則可以正確展開/銷毀C ++對象。
如果您讀了《從Jeffrey Richter調試Windows應用程序》一書,他解釋說幾乎在所有情況下,您都必須調用_beginthreadex
而不是調用CreateThread
。 _beginthread
只是圍繞一個簡化包裝_beginthreadex
。
_beginthreadex
初始化CreateThread
API不會執行的某些CRT(C運行時)內部。
如果您使用CreateThread
API而不是對CRT函數使用_begingthreadex
調用,可能會導致意外的問題。
其他答案無法討論調用包裝Win32 API函數的C運行時函數的含義。 在考慮DLL加載程序鎖定行為時,這一點很重要。
_beginthread{ex}
是否像其他答案所討論的那樣進行任何特殊的C運行時線程/光纖內存管理,它都在(假設動態鏈接到C運行時)一個可能尚未加載的DLL中實現。
從DllMain
調用_beginthread*
是不安全的。 我已經通過編寫使用Windows“ AppInit_DLLs”功能加載的DLL進行了測試。 調用_beginthreadex (...)
而不是CreateThread (...)
會導致Windows的許多重要部分在啟動過程中停止運行,因為DllMain
入口點死鎖等待加載程序鎖被釋放以便執行某些初始化任務。
順便說一句,這也是為什么kernel32.dll具有許多C運行時也重疊的字符串函數的原因-使用DllMain
函數來避免相同的情況。
兩者之間不再有區別。
關於內存泄漏等的所有注釋均基於非常舊的<VS2005版本。 幾年前,我做了一些壓力測試,可能會揭穿這個神話。 甚至Microsoft都在示例中混合了樣式,幾乎從未使用_beginthread。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.