天天看點

_beginthreadex 建立多線程解讀

一、解釋

(1)如果你正在編寫C/C++代碼,決不應該調用CreateThread。相反,應該使用VisualC++運作期庫函數_beginthreadex,退出也應該使用_endthreadex。如果不使用Microsoft的VisualC++編譯器,你的編譯器供應商有它自己的CreateThread替代函數。不管這個替代函數是什麼,你都必須使用。

(2)因為_beginthreadex和_endthreadex是CRT線程函數,是以必須注意編譯選項runtimelibaray的選擇,使用MT或MTD。[MultiThreaded , Debug MultiThreaded]。

(3)_beginthreadex函數的參數清單與CreateThread函數的參數清單是相同的,但是參數名和類型并不完全相同。這是因為Microsoft的C/C++運作期庫的開發小組認為,C/C++運作期函數不應該對Windows資料類型有任何依賴。_beginthreadex函數也像CreateThread那樣,傳回新建立的線程的句柄。

下面是關于_beginthreadex的一些要點:

1)每個線程均獲得由C/C++運作期庫的堆棧配置設定的自己的tiddata記憶體結構。(tiddata結構位于Mtdll.h檔案中的VisualC++源代碼中)。

2)傳遞給_beginthreadex的線程函數的位址儲存在tiddata記憶體塊中。傳遞給該函數的參數也儲存在該資料塊中。

3)_beginthreadex确實從内部調用CreateThread,因為這是作業系統了解如何建立新線程的唯一方法。

4)當調用CreatetThread時,它被告知通過調用_threadstartex而不是pfnStartAddr來啟動執行新線程。還有,傳遞給線程函數的參數是tiddata結構而不是pvParam的位址。

5)如果一切順利,就會像CreateThread那樣傳回線程句柄。如果任何操作失敗了,便傳回NULL。

(4)_endthreadex的一些要點:

C運作期庫的_getptd函數内部調用作業系統的TlsGetValue函數,該函數負責檢索調用線程的tiddata記憶體塊的位址。

然後該資料塊被釋放,而作業系統的ExitThread函數被調用,以便真正撤消該線程。當然,退出代碼要正确地設定和傳遞。

(5)雖然也提供了簡化版的的_beginthread和_endthread,但是可控制性太差,是以一般不使用。

(6)線程handle因為是核心對象,是以需要在最後closehandle。

(7)更多的API:

HANDLE GetCurrentProcess();

HANDLE GetCurrentThread();

DWORD GetCurrentProcessId();

DWORD GetCurrentThreadId()。

DWORD SetThreadIdealProcessor(HANDLE hThread,DWORDdwIdealProcessor);

BOOL SetThreadPriority(HANDLE hThread,int nPriority);

BOOL SetPriorityClass(GetCurrentProcess(), IDLE_PRIORITY_CLASS);

BOOL GetThreadContext(HANDLE hThread,PCONTEXTpContext);

BOOL SwitchToThread();
           

二、為什麼用_beginthreadex而不是CreateThread?

為什麼要用C運作時庫的_beginthreadex代替作業系統的CreateThread來建立線程?

你也許會說我一直用CreateThread來建立線程,一直都工作得好好的,為什麼要用_beginthreadex來代替CreateThread,下面讓我來告訴你為什麼。

首先要從标準C運作庫與多線程的沖突說起,标準C運作庫在1970年被實作了,由于當時沒任何一個作業系統提供對多線程的支援。是以編寫标準C運作庫的程式員根本沒考慮多線程程式使用标準C運作庫的情況。比如标準C運作庫的全局變量errno。很多運作庫中的函數在出錯時會将錯誤代号指派給這個全局變量,這樣可以友善調試。但如果有這樣的一個代碼片段:

if (system("notepad.exe readme.txt") == -1)
{
	switch(errno)
	{
		...//錯誤處理代碼
	}
}
           

假設某個線程A在執行上面的代碼,該線程在調用system()之後且尚未調用switch()語句時另外一個線程B啟動了,這個線程B也調用了标準C運作庫的函數,不幸的是這個函數執行出錯了并将錯誤代号寫入全局變量errno中。這樣線程A一旦開始執行switch()語句時,它将通路一個被B線程改動了的errno。這種情況必須要加以避免!因為不單單是這一個變量會出問題,其它像strerror()、strtok()、tmpnam()、gmtime()、asctime()等函數也會遇到這種由多個線程通路修改導緻的資料覆寫問題。

為了解決這個問題,Windows作業系統提供了這樣的一種解決方案——每個線程都将擁有自己專用的一塊記憶體區域來供标準C運作庫中所有有需要的函數使用。而且這塊記憶體區域的建立就是由C/C++運作庫函數_beginthreadex()來負責的。

上述代碼中的

errno

全局變量,在最新的代碼中已經變成了一個宏

#define errno (*_errno())

,最終調用的代碼函數如下:

int * __cdecl _errno(void)
{
   //線程相關
    _ptiddata ptd = _getptd_noexit();
    if (!ptd) {
        return &ErrnoNoMem;
    } else {
        return ( &ptd->_terrno );
    }
}
           

這裡的errno不在是全局變量,而是函數;該函數傳回每個線程各自errno。是以,解決了資料覆寫問題。

為了印象深刻,我們可以檢視該函數的具體實作,_beginthreadex的實作可以在vs2008安裝目錄下找到,其檔案和路徑如下:

...\VC\crt\src\threadex.c
           

簡言而之,_beginthreadex在内部調用了CreateThread,在調用之前_beginthreadex做了很多的工作,進而使得它比CreateThread更安全。是以,在實際編碼中,我們建立線程需要使用_beginthreadex函數。

參考文章:

原文:https://blog.csdn.net/laoyang360/article/details/7720656

原文:https://blog.csdn.net/morewindows/article/details/7421759