-
CreateThread
鎖定
CreateThread是一種微軟在Windows API中提供了建立新的線程的函數,該函數在主線程的基礎上創建一個新線程。線程終止運行後,線程對象仍然在系統中,必須通過CloseHandle函數來關閉該線程對象。
需要調用到CRT庫時,不要用CreateThread 創建線程、並用CloseHandle來關閉這個線程,而應該用_beginthread來創建線程,_endthread來銷燬線程。因為沒有對子線程為CRT庫分配堆,會導致低內存錯誤而崩潰。
- 中文名
- CreateThread
- 外文名
- CreateThread
- 聲明位置
- processthreadsapi.h
- 返回值類型
- HANDLE
- 調用方式
- __stdcall
CreateThread步驟
CreateThread將在主線程的基礎上創建一個新線程,大致做如下步驟:
1.在內核對象中分配一個線程標識/句柄,可供管理,由CreateThread返回
2.把線程退出碼置為STILL_ACTIVE,把線程掛起計數置1
3.分配context結構
4.分配兩頁的物理存儲以準備棧,保護頁設置為PAGE_READWRITE,第2頁設為PAGE_GUARD
5.lpStartAddr和lpvThread值被放在棧頂,使它們成為傳送給StartOfThread的參數
6.把context結構的棧指針指向棧頂(第5步)指令指針指向startOfThread函數
CreateThread函數原型
MSDN中CreateThread原型:
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,//SD
SIZE_T dwStackSize,//initialstacksize
LPTHREAD_START_ROUTINE lpStartAddress,//threadfunction
DWORD dwCreationFlags,//creationoption
LPDWORD lpThreadId//threadidentifier
)
processthreadsapi.h中CreateThread原型:
WINBASEAPI
_Ret_maybenull_
HANDLE
WINAPI
CreateThread(
_In_opt_LPSECURITY_ATTRIBUTES lpThreadAttributes,
_In_SIZE_T dwStackSize,
_In_LPTHREAD_START_ROUTINE lpStartAddress,
_In_opt___drv_aliasesMemLPVOID lpParameter,
_In_DWORD dwCreationFlags,
_Out_opt_LPDWORD lpThreadId
);
CreateThread參數説明
lpThreadAttributes:指向SECURITY_ATTRIBUTES型態的結構的指針。在Windows 98中忽略該參數。在Windows NT中,NULL使用默認安全性,不可以被子線程繼承,否則需要定義一個結構體將它的bInheritHandle成員初始化為TRUE
dwStackSize,設置初始棧的大小,以字節為單位,如果為0,那麼默認將使用與調用該函數的線程相同的棧空間大小。任何情況下,Windows根據需要動態延長堆棧的大小。
lpStartAddress,指向線程函數的指針,形式:@函數名,函數名稱沒有限制,
線程有兩種聲明方式
(1)DWORD WINAPI 函數名 (LPVOID lpParam); //標準格式
DWORD WINAPI 函數名 (LPVOID lpParam) { return 0; } CreateThread(NULL, 0, 函數名, 0, 0, 0);
(2)void 函數名();
使用void 函數名()此種線程聲明方式時,lpStartAddress需要加入LPTHREAD_START_ROUTINE轉換,如
void 函數名() { return; } CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)函數名, 0, 0, 0);
lpParameter:向線程函數傳遞的參數,是一個指向結構的指針,不需傳遞參數時,為NULL。
dwCreationFlags :線程標誌,可取值如下
(1)CREATE_SUSPENDED(0x00000004):創建一個掛起的線程,
(2)0:表示創建後立即激活。
(3)STACK_SIZE_PARAM_IS_A_RESERVATION(0x00010000):dwStackSize參數指定初始的保留堆棧 的大小,否則,dwStackSize指定提交的大小。該標記值在Windows 2000/NT and Windows Me/98/95上不支持。
lpThreadId:保存新線程的id。
返回值:函數成功,返回線程句柄;函數失敗返回false。若不想返回線程ID,設置值為NULL。
函數説明:
創建一個線程。
語法:
hThread = CreateThread (&security_attributes, dwStackSize, ThreadProc,pParam, dwFlags, &idThread) ;
一般並不推薦使用 CreateThread函數,而推薦使用RTL庫裏的System單元中定義的 BeginThread函數,因為這除了能創建一個線程和一個入口函數以外,還增加了幾項保護措施。
在MFC程序中,應該調用AfxBeginThread函數,在Visual C++程序中應調用_beginthreadex函數。
CreateThread內存泄漏
其實,真正的原因並非如此。看如下一段代碼:
HANDLECreateThread( LPSECURITY_ATTRIBUTESlpThreadAttributes,//線程安全屬性 DWORDdwStackSize,//堆棧大小 LPTHREAD_START_ROUTINElpStartAddress,//線程函數 LPVOIDlpParameter,//線程參數 DWORDdwCreationFlags,//線程創建屬性 LPDWORDlpThreadId//線程ID );
CloseHandle函數的原型是:
BOOL CloseHandle( HANDLE hObject );//HANDLE hObject 對象句柄
CloseHandle可以關閉多種類型的對象,比如文件對象等,這裏使用這個函數來關閉線程對象。調用時,hObject為待關閉的線程對象的句柄。
説使用這種方法可能會引發內存泄漏問題,其實不完全正確。那為什麼會引起內存的泄漏呢?因為當線程的函數用到了C的標準庫的時候,很容易導致衝突,所以在創建VC的工程時,系統提示是用單線程還是用多線程的庫,因為在C的內部有很多的全局變量。例如,出錯號、文件句柄等全局變量。
因為在C的庫中有全局變量,這樣用C的庫時,如果程序中使用了標準的C程序庫時,就很容易導致運行不正常,會引起很多的衝突。所以,微軟和Borland都對C的庫進行了一些改進。但是這個改進的一個條件就是,如果一個線程已經開始創建了,就應該創建一個結構來包含這些全局變量,接着把這些全局變量放入線程的上下文中和這個線程相關起來。這樣,全局變量就會依賴於這個線程,不會引起衝突。
這樣做就會有一個問題,什麼時候這個線程開始創建呢?標準的Windows的API是不知道的,因為它是靜態的庫。這些庫都是放在VC的LIB的目錄內的,而線程函數是操作系統的函數。所以,VC和BC在創建線程時,都會用_beginThread來創建線程,再用_endThread來結束線程。這樣,它們在創建線程的時候,就會知道什麼時候創建了線程,並把全局變量放入某一結構中,讓它和線程能關聯起來。這樣就不會發生衝突了。
很顯然,要完成這個功能,首先需要分配結構表把全局變量包含起來。這個過程是在_beginThread時做的,而釋放則是在_endTread內完成。
所以,當用_beginThread來創建,而用CloseHandle來關閉線程時,這時複製的全局結構就不會被釋放了,這就有了內存的泄漏。這就是很多資料所説的內存泄漏問題的真正的原因。
其實,可以不用_beginThread和_endThread這一對函數。如果用CreateThread函數創建,用CloseHandle關閉,那麼,與C有關的庫就會用全局的,它們會引起衝突。所以,比較好的方法就是在線程內不用標準的C的庫(可以使用Windows API的庫函數)。這樣就不會有什麼問題,也就不會引起衝突。例如,字符串的操作函數、文件操作等。
當某個程序創建一個線程後,會產生一個線程的句柄,線程的句柄主要用來控制整個線程的運行,例如停止、掛起或設置線程的優先級等操作。
(這是VC6.0的早期BUG,後來的vs版本都修復了這個漏洞。老問題不值得重談!)
CreateThread示例
CreateThread 函數從一個進程裏面創建一個線程。這個開始的線程必須指定開始執行代碼的地址,新線程執行。有代表性的,開始地址就是一個函數名。這個函數有一個參數,並且返回一個 DWORD 值。一個進程裏面同時有多個線程在執行。
被調用的線程用 WaitForMultipleObjects 持續等待,直到所有的工作線程退出。在線程退出後,關掉線程函數的句柄。
#include <windows.h> #include <strsafe.h>//win2003SDK必須安裝 要不無此頭文件。此文件是為了實現StringCchPrintf,StringCchLength。 #define MAX_THREADS 3 #define BUF_SIZE 255 typedef struct _MyData{ int val1; int val2; }MYDATA,*PMYDATA; DWORD WINAPI ThreadProc(LPVOID lpParam) { HANDLE hStdout; PMYDATA pData; TCHAR msgBuf[BUF_SIZE]; size_tcchStringSize; DWORD dwChars; hStdout=GetStdHandle(STD_OUTPUT_HANDLE); if(hStdout==INVALID_HANDLE_VALUE) return 1; //Cast the parameter to the correct data type. pData=(PMYDATA)lpParam; //Print the parameter values using thread-safe functions. StringCchPrintf(msgBuf,BUF_SIZE,TEXT("Parameters=%d,%d\n"), pData->val1,pData->val2); StringCchLength(msgBuf,BUF_SIZE,&cchStringSize); WriteConsole(hStdout,msgBuf,cchStringSize,&dwChars,NULL); //Free the memory allocated by the caller for the thread //data structure. HeapFree(GetProcessHeap(),0,pData); return 0; } void main() { PMYDATA pData; DWORD dwThreadId[MAX_THREADS]; HANDLE hThread[MAX_THREADS]; int i; //Create MAX_THREADS worker threads. for(i = 0; i < MAX_THREADS; i++) { //Allocate memory for thread data. pData=(PMYDATA)HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY, sizeof(MYDATA)); if(pData==NULL) ExitProcess(2); //Generate unique data for each thread. pData->val1=i; pData->val2=i+100; hThread[i]=CreateThread( NULL,//default security attributes 0,//use default stack size ThreadProc,//thread function pData,//argument to thread function 0,//use default creation flags &dwThreadId[i]);//returns the thread identifier //Check there turn value for success. if(hThread[i]==NULL) { ExitProcess(i); } } //Wait until all threads have terminated. WaitForMultipleObjects(MAX_THREADS,hThread,TRUE,INFINITE); //Close all thread handle supon completion. for(i=0;i<MAX_THREADS;i++) { CloseHandle(hThread[i]); } }