簡體   English   中英

Windows線程:_beginthread與_beginthreadex與CreateThread C ++

[英]Windows threading: _beginthread vs _beginthreadex vs CreateThread C++

有什么更好的方式啟動_beginthread_beginthreadxCreateThread線程?

我正在嘗試確定_beginthread_beginthreadexCreateThread的優點/缺點是什么。 所有這些函數都將線程句柄返回到新創建的線程,我已經知道,當發生錯誤時(可以通過調用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_ATTACHDLL_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_beginthreadxCreateThread

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可以:

  1. 指定安全屬性。
  2. 以掛起狀態啟動線程。
  3. 您可以獲得可以與OpenThread一起使用的線程ID。
  4. 如果調用成功,則保證返回的線程句柄有效。 在那里,您需要使用CloseHandle關閉手柄。
  5. 返回的線程句柄可以與同步API一起使用。

_beginthreadex非常類似於CreateThread ,但是前者是CRT實現,后者是Windows API調用。 CreateThread的文檔包含以下建議:

可執行文件中的調用C運行時庫(CRT)的線程應使用_beginthreadex_endthreadex函數進行線程管理,而不是CreateThreadExitThread 這需要使用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調用,可能會導致意外的問題。

查閱這份來自Richter的舊Microsoft Journal。

其他答案無法討論調用包裝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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM