推薦參考博客:秒殺多線程第二篇 多線程第一次親密接觸 CreateThread與_beginthreadex本質區別
CreateThread:Windows的API函數(SDK函數的標准形式,直截了當的創建方式,任何場合都可以使用),提供操作系統級別的創建線程的操作,且僅限於工作者線程
beginthread beginthreadex:MS對C Runtime庫的擴展SDK函數,首先針對C Runtime庫做了一些初始化的工作,以保證C Runtime庫工作正常,然后,調用CreateThread真正創建線程。beginthread是_beginthreadex的功能子集,雖然_beginthread內部是調用_beginthreadex但他屏蔽了象安全特性這樣的功能,例如,如果使用_beginthread,就無法創建帶有安全屬性的新線程,無法創建暫停的線程,也無法獲得線程的ID值。_beginthread與CreateThread不是同等級別,_beginthreadex和CreateThread在功能上完全可替代
AfxBeginThread:MFC中線程創建的MFC函數,它簡化了操作或讓線程能夠響應消息,即可用於界面線程,也可以用於工作者線程,但要注意盡量不要在一個MFC程序中使用_beginthreadex()或CreateThread()。
AfxBeginThread、BeginThread和BeginThreadex實際上是編譯器對CreateThread的封裝
.編程的時候如何選擇各個函數
1 MFC程序選擇AfxBeginThread當然不容置疑
2 如果不使用Microsoft的Visual C++編譯器,你的編譯器供應商有它自己的CreateThred替代函數
3 盡量不要調用CreateThread。相反,應該使用Visual C++運行期庫函數_beginthreadex,原因如下:
考慮標准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
這樣的提示!
對於線程的支持是后來的事!
這也導致了許多CRT的函數在多線程的情況下必須有特殊的支持,不能簡單的使用CreateThread就OK。
大多的CRT函數都可以在CreateThread線程中使用,看資料說只有signal()函數不可以,會導致進程終止!但可以用並不是說沒有問題!
有些CRT的函數象malloc(), fopen(), _open(), strtok(), ctime(), 或localtime()等函數需要專門的線程局部存儲的數據塊,這個數據塊通常需要在創建線程的時候就建立,如果使用CreateThread,這個數據塊就沒有建立,然后會怎樣呢?在這樣的線程中還是可以使用這些函數而且沒有出錯,實際上函數發現這個數據塊的指針為空時,會自己建立一個,然后將其與線程聯系在一起,這意味着如果你用CreateThread來創建線程,然后使用這樣的函數,會有一塊內存在不知不覺中創建,遺憾的是,這些函數並不將其刪除,而CreateThread和ExitThread也無法知道這件事,於是就會有Memory leak,在線程頻繁啟動的軟件中(比如某些服務器軟件),遲早會讓系統的內存資源耗盡!
_beginthreadex和_endthreadex就對這個內存塊做了處理,所以沒有問題!(不會有人故意用CreateThread創建然后用_endthreadex終止吧,而且線程的終止最好不要顯式的調用終止函數,自然退出最好!)
如果在除主線程之外的任何線程中進行一下操作,你就應該使用多線程版本的C runtime library,並使用_beginthreadex和_endthreadex:
1 使用malloc()和free(),或是new和delete
2 使用stdio.h或io.h里面聲明的任何函數
3 使用浮點變量或浮點運算函數
4 調用任何一個使用了靜態緩沖區的runtime函數,比如:asctime(),strtok()或rand()
Handle的問題,_beginthread的對應函數_endthread自動的調用了CloseHandle,而_beginthreadex的對應函數_endthreadex則沒有,所以CloseHandle無論如何都是要調用的不過_endthread可以幫你執行自己不必寫,其他兩種就需要自己寫!(Jeffrey Richter強烈推薦盡量不用顯式的終止函數,用自然退出的方式,自然退出當然就一定要自己寫CloseHandle)
轉載自C++多線程實例(_beginThreadex創建多線程)
二解釋
1)如果你正在編寫C/C++代碼,決不應該調用CreateThread。相反,應該使用VisualC++運行期庫函數_beginthreadex,推出也應該使用_endthreadex。如果不使用Microsoft的VisualC++編譯器,你的編譯器供應商有它自己的CreateThred替代函數。不管這個替代函數是什么,你都必須使用。
2)因為_beginthreadex和_endthreadex是CRT線程函數,所以必須注意編譯選項runtimelibaray的選擇,使用MT或MTD。
3) _beginthreadex函數的參數列表與CreateThread函數的參數列表是相同的,但是參數名和類型並不完全相同。這是因為Microsoft的C/C++運行期庫的開發小組認為,C/C++運行期函數不應該對Windows數據類型有任何依賴。_beginthreadex函數也像CreateThread那樣,返回新創建的線程的句柄。
下面是關於_beginthreadex的一些要點:
&8226;每個線程均獲得由C/C++運行期庫的堆棧分配的自己的tiddata內存結構。(tiddata結構位於Mtdll.h文件中的VisualC++源代碼中)。
&8226;傳遞給_beginthreadex的線程函數的地址保存在tiddata內存塊中。傳遞給該函數的參數也保存在該數據塊中。
&8226;_beginthreadex確實從內部調用CreateThread,因為這是操作系統了解如何創建新線程的唯一方法。
&8226;當調用CreatetThread時,它被告知通過調用_threadstartex而不是pfnStartAddr來啟動執行新線程。還有,傳遞給線程函數的參數是tiddata結構而不是pvParam的地址。
&8226;如果一切順利,就會像CreateThread那樣返回線程句柄。如果任何操作失敗了,便返回NULL。
4) _endthreadex的一些要點:
&8226;C運行期庫的_getptd函數內部調用操作系統的TlsGetValue函數,該函數負責檢索調用線程的tiddata內存塊的地址。
&8226;然后該數據塊被釋放,而操作系統的ExitThread函數被調用,以便真正撤消該線程。當然,退出代碼要正確地設置和傳遞。
5)雖然也提供了簡化版的的_beginthread和_endthread,但是可控制性太差,所以一般不使用。
6)線程handle因為是內核對象,所以需要在最后closehandle。
7)更多的API:
HANDLE GetCurrentProcess();
HANDLE GetCurrentThread();
DWORD GetCurrentProcessId();
DWORD GetCurrentThreadId()。
DWORD SetThreadIdealProcessor(HANDLE hThread,DWORD dwIdealProcessor);
BOOL SetThreadPriority(HANDLE hThread,int nPriority);
BOOL SetPriorityClass(GetCurrentProcess(), IDLE_PRIORITY_CLASS);
BOOL GetThreadContext(HANDLE hThread,PCONTEXT pContext);BOOL SwitchToThread();
三注意
1)C++主線程的終止,同時也會終止所有主線程創建的子線程,不管子線程有沒有執行完畢。所以上面的代碼中如果不調用WaitForSingleObject,則2個子線程t1和t2可能並沒有執行完畢或根本沒有執行。
2)如果某線程掛起,然后有調用WaitForSingleObject等待該線程,就會導致死鎖。所以上面的代碼如果不調用resumethread,則會死鎖。
為什么要用C運行時庫的_beginthreadex代替操作系統的CreateThread來創建線程?
來源自自1999年7月MSJ雜志的《Win32 Q&A》欄目
你也許會說我一直用CreateThread來創建線程,一直都工作得好好的,為什么要用_beginthreadex來代替CreateThread,下面讓我來告訴你為什么。
回答一個問題可以有兩種方式,一種是簡單的,一種是復雜的。
如果你不願意看下面的長篇大論,那我可以告訴你簡單的答案:_beginthreadex在內部調用了CreateThread,在調用之前_beginthreadex做了很多的工作,從而使得它比CreateThread更安全。
_beginthreadex用法
頭文件:process.h
函數原型:unsigned long _beginthreadex( void *security, unsigned stack_size, unsigned ( __stdcall *start_address )( void * ), void *arglist, unsigned initflag, unsigned *thrdaddr );
//第1個參數:安全屬性,NULL為默認安全屬性
//第2個參數:指定線程堆棧的大小。如果為0,則線程堆棧大小和創建它的線程的相同。一般用0
//第3個參數:指定線程函數的地址,也就是線程調用執行的函數地址(用函數名稱即可,函數名稱就表示地址,注意的是函數訪問方式一定是__stdcall,函數返回值一定是unsigned,函數參數一定是void*)
//第4個參數:傳遞給線程的參數的指針,可以通過傳入對象的指針,在線程函數中再轉化為對應類的指針
//第5個參數:線程初始狀態,0:立即運行;CREATE_SUSPEND:懸掛(如果出事狀態定義為懸掛,就要調用ResumeThread(HANDLE) 來激活線程的運行)
//第6個參數:用於記錄線程ID的地址
使用舉例
#include<string> #include<iostream> #include<process.h> #include<windows.h> using namespace std; struct Arg//用來傳參給線程函數 { double d_; string str_; Arg(double dd, string ss):d_(dd), str_(ss){} }; //線程綁定的函數返回值和參數是確定的,而且一定要__stdcall unsigned __stdcall threadFun(void *) { for(int i = 0; i < 10; i++) cout<<i<<endl; return 1; } //可以通過結構體來傳入參數 unsigned __stdcall threadFunArg(void *arglist) { Arg *p = (Arg *)arglist; cout<<p->d_<<endl; cout<<p->str_<<endl; return 2; } //簡單的線程類 class ThreadClass { private: string str_; int i_; public: ThreadClass(string s, int i):str_(s), i_(i){} static unsigned __stdcall threadStaic(void *arg) { ThreadClass *p = (ThreadClass *)arg; p->threadfun(); return 3; } void threadfun() { cout<<str_<<endl; cout<<i_<<endl; } }; int main() { unsigned int thID1, thID2, thID3, thID4; HANDLE hth1, hth2, hth3, hth4; Arg arg(3.14, "hello world"); ThreadClass tclass("welcom", 999); //注意的是_beginthreadex是立即返回的,系統不會等線程函數執行完畢,因此要保證 //局部arg變量 在線程函數執行完畢前不會釋放,更安全的是使用new來構造arg hth1 = (HANDLE)_beginthreadex(NULL, 0, threadFun, NULL, 0, &thID1); hth2 = (HANDLE)_beginthreadex(NULL, 0, threadFun, NULL, 0, &thID2); hth3 = (HANDLE)_beginthreadex(NULL, 0, threadFunArg, &arg, 0, &thID3); hth4 = (HANDLE)_beginthreadex(NULL, 0, ThreadClass::threadStaic, &tclass, 0, &thID4); //主線程一定要等待子線程結束 WaitForSingleObject(hth1, INFINITE); WaitForSingleObject(hth2, INFINITE); WaitForSingleObject(hth3, INFINITE); WaitForSingleObject(hth4, INFINITE); DWORD exitCode1, exitCode2, exitCode3, exitCode4; GetExitCodeThread(hth1, &exitCode1); GetExitCodeThread(hth2, &exitCode2); GetExitCodeThread(hth3, &exitCode3); GetExitCodeThread(hth4, &exitCode4); cout<<endl<<"exitcode::"<<exitCode1<<" "<<exitCode2<<" "<<exitCode3<<" " <<exitCode4<<endl; cout<<"ID:"<<thID1<<" "<<thID2<<" "<<thID3<<" "<<thID4<<endl; //一定要記得關閉線程句柄 CloseHandle(hth1); CloseHandle(hth2); CloseHandle(hth3); CloseHandle(hth4); }
【版權聲明】轉載請注明出處:http://www.cnblogs.com/TenosDoIt/archive/2013/04/15/3022036.html