在Windows的多線程編程中,建立線程的函數主要有CreateThread,_beginthead(_beginthreadex)和AfxBeginThread,那麼它們之間有什麼聯繫與區別呢?當我須要建立一個線程時該用哪一個函數呢?編程
下面先介紹各個函數的用法:安全
CreateThread:數據結構
函數原型:多線程
參數:
lpThreadAttributes: 指向一個LPSECURITY_ATTRIBUTES結構的指針決定返回的句柄可否被繼承,若是lpThreadAttributes爲空,這個句柄不能被繼承。
sdStackSize:初始化的堆棧大小,以字節爲單位。若是爲0,使用默認的大小。
lpStartAddress:函數的入口地址,是一個函數指針。
lpParameter:一個指針,被傳遞到線程函數裏。
dwCreationFlags:線程建立的標誌。若是爲CREATE_SUSPENDED這個標誌,那麼須要使用ResumeThread函數來激活線程函數,若是爲NULL,線程函數馬上執行。
IpThreadId:一個指向線程id的指針,若是爲空,線程id不被返回。
返回值:
1:若是函數成功執行,返回值將是這個新線程的句柄。若是失敗,返回值是NULL。
2:當線程函數的起始地址無效(或者不可訪問)時,CreateThread函數仍可能成功返回。若是該起始地址無效,則當線程運行時,異常將發生,線程終止,並返回一個錯誤代碼,可使用GetLastError獲取。
說明:
1:若是線程函數return,返回值會隱式條用ExitThread函數,可使用GetExitCodeThread函數得到該線程函數的返回值。
2:使用CreateThread建立的線程具備THREAD_PRIORITY_NORMAL線程優先級。可使用GetThreadPriority和SetThreadPriority函數獲取和設置線程優先級值。
3:當一個線程結束時,這個線程的對象將得到有信號狀態,使得任何等待這個對象的線程都可以成功並繼續執行下去。
4:系統中的線程對象一直存活到線程結束,而且全部指向它的句柄都須要經過調用CloseHandle關閉。
5:若是一個線程調用了CRT,應該使用_beginthreadex 和_endthreadex(須要使用多線程版的CRT)。函數
_beginthread與_beginthreadex:
函數原型:ui
參數:
start_address:線程函數的入口地址。對於_beginthread,線程函數的調用約定是_cdecl。對於_beginthreadex,線程函數的調用約定是_stdcall。
stack_size:線程堆棧大小,能夠爲0。
arglist:傳遞給線程函數的參數,能夠爲0。
security:線程安全屬性。
initflag:線程建立的初始標誌。爲CREATE_SUSPENDED則掛起線程,使用ResumeThread激活線程,爲NULL則當即執行。
thrdaddr:線程Id。
返回值:
1:若是成功,將會返回一個新的線程句柄。然而,若是線程函數執行的很快,_beginthread可能獲得一個非法的句柄。
2:若是失敗,_beginthread返回-1,此時errno變量將被設置。_beginthreadex返回0,此時errno和_doserrno都被設置。
說明:
1:_beginthread函數的線程入口函數必須使用_cdecl調用約定。_beginthreadex函數的線程入口函數必須使用_stdcall調用約定
2:使用_beginthreadex比使用_begingthread更加安全。由於_beginthread的線程函數可能執行很快,這時可能會返回一個非法的句柄。
3:_endthread將會自動的關閉線程句柄,然而_beginthreadex不會,須要使用CloseHandle現實的關閉句柄。因此_beginthreadex函數可使用WaitForSingleObject函數來獲取線程對象來進行同步。
4:一個鏈接Libcmt.lib的可執行文件,不要調用ExitThread函數,這個函數會阻止系統的運行時回收已分配的資源。使用_endthread and _endthreadex能夠回收已分配的資源而後再調用ExitThread.
5: 能夠調用_endthread和_endthreadex顯示式結束一個線程。然而,當線程函數返回時,_endthread和_endthreadex 被自動調用。endthread和_endthreadex的調用有助於確保分配給線程的資源的合理回收。
6:當_beginthread和_beginthreadex被調用時,操做系統本身處理線程棧的分配。若是在調用這些函數時,指定棧大小爲0,則操做系統 爲該線程建立和主線程大小同樣的棧。若是任何一個線程調用了abort、exit或者ExitProcess,則全部線程都將被終止。
7:對於使用C運行時庫裏的函數的線程應該使用_beginthread和_endthread這些C運行時函數來管理線程,而不是使用CreateThread和ExitThread。不然,當調用ExitThread後,可能引起內存泄露。
8:必須使用多線程版的 C run_time libraries.spa
AfxBeginThread:
函數原型:操作系統
參數:
pfnThreadProc:線程函數的入口地址。
函數原型:UINT __cdecl MyControllingFunction( LPVOID pParam );
pThreadClass:繼承CWinThread類的RUNTIME_CLASS對象。
pParam: 傳遞給線程函數的參數,能夠爲0。
nPriority:線程優先級。
nStackSize:指明線程堆棧的大小,以字節爲單位,能夠爲0。
dwCreateFlags:線程建立標誌。
lpSecurityAttrs:線程安全屬性。
返回值:
若是成功則返回一個指針指向線程對象,不然爲NULL。
說明:
能夠調用AfxEndThread來終止線程或者return。
.net
下面來介紹下這幾個函數的聯繫與區別:
CreateThread:
CreateThread是Windows的API函數,提供操做系統級別的建立線程的操做。_beginthread(及_beginthreadex)與AfxBeginThread的底層實現都調用了CreateThread函數。
CreateThread函數沒有考慮到下面二點:
(1)C Runtime中須要對多線程進行記錄和初始化,以保證C函數庫工做正常(典型的例子就是strtok函數)
(2)MFC也須要知道新線程的建立,也須要作一些初始化工做。
因此,在不調用MFC和CRT的函數時,能夠用CreateThread建立線程,其它狀況不要使用。
AfxBeginThread:
MFC中線程建立的函數,首先建立了相應的CWinThread對象,而後調用CWinThread::CreateThread,在CWinThread::CreateThread中完成了對線程對象的初始化工做,而後調用_beginthreadex建立線程。注意不要在一個MFC程序中使用_beginthreadex()或CreateThread()。
_beginthread和_beginthreadex: (實現文件分別是thread.c和threadex.c)
是MS對C Runtime庫的擴展SDK函數,首先對C Runtime庫作了一些初始化的工做,以保證C Runtime庫工做正常。而後,調用CreateThread真正建立線程。
若要使多線程C和C++程序可以正確地運行,必須建立一個數據結構,並將它與使用C/C++運行期庫函數的每一個線程關聯起來。當你調用C/C++運行期庫時,這些函數必須知道查看調用線程的數據塊,這樣就不會對別的線程產生不良影響。
1.每一個線程均得到由C/C++運行期庫的堆棧分配的本身的tiddata內存結構。
2.傳遞給_beginthreadex的線程函數的地址保存在tiddata內存塊中。傳遞給該函數的參數也保存在該數據塊中。(指向tiddata結構的指針會做爲一個TLS保存起來)
3._beginthreadex確實從內部調用CreateThread,由於這是操做系統瞭解如何建立新線程的惟一方法。
4.當調用CreatetThread時,它被告知經過調用_threadstartex而不是pfnStartAddr來啓動執行新線程。還有,傳遞給線程函數的參數是tiddata結構而不是pvParam的地址。
5.若是一切順利,就會像CreateThread那樣返回線程句柄。若是任何操做失敗了,便返回 NULL。
總結:
1:CreateThread是由操做系統提供的接口,而AfxBeginThread和_BeginThread則是編譯器對它的封裝。
2:用_beginthreadex()、_endthreadex函數應該是最佳選擇,且都是C Run-time Library中的函數,函數的參數和數據類型都是C Run-time Library中的類型,這樣在啓動線程時就不須要進行Windows數據類型和C Run-time Library中的數據類型之間的轉化,從而減低了線程啓動時的資源消耗和時間的消耗。
3:MFC也是C++類庫(只不過是Microsoft的C++類庫,不是標準的C++類庫),在MFC中也封裝了new和delete兩中運算符,因此用到new和delete的地方不必定非要使用_beginthreadex() 函數,用其餘兩個函數均可以。
4:_beginthreadex和_beginthread在回調入口函數以前進行一些線程相關的CRT的初始化操做。CRT的函數庫在線程出現以前就已經存在,因此原有的CRT不能真正支持線程,這也致使了許多CRT的函數在多線程的狀況下必須有特殊的支持,不能簡單的使CreateThread就能夠。
5:若是要做多線程(非MFC)程序,在主線程之外的任何線程內
使用malloc(),free(),new
調用stdio.h或io.h,包括fopen(),open(),getchar(),write(),printf(),errno
使用浮點變量和浮點運算函數
調用那些使用靜態緩衝區的函數如: asctime(),strtok(),rand()等。
應該使用多線程的CRT並配合_beginthreadex(該函數只存在於多線程CRT), 其餘狀況,你可使用單線程的CRT並配合CreateThread。由於對產生的線程而言,_beginthreadex比CreateThread會爲上述操做多作額外的工做,好比幫助strtok()爲每一個線程準備一份緩衝區。
然而多線程程序極少狀況不使用上述那些函數(好比內存分配或者io),因此與其每次都要思考是要使用_beginthreadex仍是CreateThread,不如就一棍子敲定_beginthreadex。
6:你也許會藉助win32來處理內存分配和io,這時候你確實能夠以單線程crt配合CreateThread,由於io的重任已經從crt轉交給了win32。這時一般你應該使用HeapAlloc,HeapFree來處理內存分配,用CreateFile或者GetStdHandle來處理io。
7:還有一點比較重要的是_beginthreadex傳回的雖然是個unsigned long,實際上是個線程Handle(事實上_beginthreadex在內部就是調用了CreateThread),因此你應該用CloseHandle來結束他。千萬不要使用ExitThread()來退出_beginthreadex建立的線程,那樣會喪失釋放簿記數據的機會,應該使用_endthreadex.
下面對兩個概念進行闡述
CRT(C/C++ Runtime Library):
是一種函數庫,由編譯器的生產廠家提供頭文件或接口,操做系統提供運行時庫的實現。因此Windows和Linux系統的運行時庫函數接口雖然同樣,但具體實現不同。
CRT是支持C/C++運行的一系列函數和代碼的總稱,雖然沒有一個很精確的定義,可是能夠知道,你的main函數就是它負責調用的,還有平時使用的strlen,strtok,time,atoi之類的函數也是它提供的。
線程局部存儲(TLS,thread local storage)
一個多線程程序中,全局變量(及分配的內存)被全部線程所共享。函數的靜態局部變量也被全部使用該函數的線程所共享。一個函數中的自動變量對每個線程是惟一的,由於它們存儲於堆棧上,而每一個線程都有他們本身的堆棧。有時,咱們須要對每個線程惟一的持續性存儲。例如,C函數strtok就須要這種存儲。不幸的是,C語言不支持這種變量。可是Windows提供了四個API函數來實現這種機制。咱們把這種存儲稱爲線程局部存儲(TLS,Thread Local Storage)。
首先,定義一個結構,把對每一個線程惟一的數據包含在該結構中。
例如:
typedef struct
{
int one;
int two;
} DATA, *PDATA;
而後,主線程調用TlsAlloc函數來爲進程得到一個TLS索引:tlsIndex = TlsAlloc();該TLS索引能夠存儲於一個全局變量或者經過線程函數的參數傳遞給其它線程。每一個須要使用該TLS索引的線程,先動態分配內存,而後調用TlsSetValue函數將該內存關聯到該TLS索引(及該線程): TlsSetValue(tlsIndex, GlobalAlloc(GPTR, sizeof(DATA));
此時,線程直接或間接調用的函數能夠經過以下方式得到該線程的TLS存儲區域:
PDATA pdata;
pdata = (PDATA) TlsGetValue(tlsIndex);
此時,就可使用該線程的TLS存儲區的變量了。
當線程函數終止時,它應該釋放它所分配的動態空間: GlobalFree(TlsGetValue(tlsIndex));
當全部使用TLS的線程都終止後,主線程應當釋放該TLS存儲空間: TlsFree(tlsIndex);
TLS能夠以一種更簡單的方式使用,那就是經過Winodws對C所做的擴展關鍵字__declspec和擴展存儲類型修飾符thread。例如:
__declspec(thread) int global_tls_i = 1; // 在函數外部,聲明一個TLS變量
__declspec(thread) static int local_tls_i = 2; // 在函數內部聲明一個靜態TLS變量線程