推薦參考博客:秒殺多線程第二篇 多線程第一次親密接觸 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的地址
使用舉例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
|
#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