CreateThread

CreateThread

CreateThread是一種微軟在Windows API中提供了建立新的執行緒的函式,該函式在主執行緒的基礎上創建一個新執行緒。執行緒終止運行後,執行緒對象仍然在系統中,必須通過CloseHandle函式來關閉該執行緒對象。 需要調用到CRT庫時,不要用CreateThread 創建執行緒、並用CloseHandle來關閉這個執行緒,而應該用_beginthread來創建執行緒,_endthread來銷毀執行緒。因為沒有對子執行緒為CRT庫分配堆,會導致低記憶體錯誤而崩潰。 CreateThread 不會判斷lpStartAddr是數據還是代碼,甚至不會判斷是否有足夠的訪問許可權。lpStartAddr可以未必是個函式,也可以是類成員,只要將函式指針強制轉換,並且不產生棧溢出和沒有訪問許可權的問題就以及類如未定義的指令之類的錯誤可以順利執行執行緒。創建類成員函式的對象時,this指針是調用CreateThread時所處的類對象的指針。在類對象外調用,其this指針將是未知的。

步驟

CreateThread將在主執行緒的基礎上創建一個新執行緒,大致做如下步驟:

1.在核心對象中分配一個執行緒標識/句柄,可供管理,由CreateThread返回

2.把執行緒退出碼置為STILL_ACTIVE,把執行緒掛起計數置1

3.分配context結構

4.分配兩頁的物理存儲以準備棧,保護頁設定為PAGE_READWRITE,第2頁設為PAGE_GUARD

5.lpStartAddr和lpvThread值被放在棧頂,使它們成為傳送給StartOfThread的參數

6.把context結構的棧指針指向棧頂(第5步)指令指針指向startOfThread函式

函式原型

MSDN中CreateThread原型:

HANDLE CreateThread(

LPSECURITY_ATTRIBUTES lpThreadAttributes,//SD

SIZE_T dwStackSize,//initialstacksize

LPTHREAD_START_ROUTINE lpStartAddress,//threadfunction

LPVOID lpParameter,//threadargument

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

);

參數說明

lpThreadAttributes:指向SECURITY_ATTRIBUTES型態的結構的指針。在Windows 98中忽略該參數。在Windows NT中,NULL使用默認安全性,不可以被子執行緒繼承,否則需要定義一個結構體將它的bInheritHandle成員初始化為TRUE

dwStackSize,設定初始棧的大小,以位元組為單位,如果為0,那么默認將使用與調用該函式的執行緒相同的棧空間大小。任何情況下,Windows根據需要動態延長堆疊的大小。

lpStartAddress,指向執行緒函式的指針,形式:@函式名,函式名稱沒有限制,但是必須以下列形式聲明:

DWORD WINAPI 函式名 (LPVOID lpParam) ,格式不正確將無法調用成功。

//也可以直接調用void類型

//但lpStartAddress要這樣通過LPTHREAD_START_ROUTINE轉換如: (LPTHREAD_START_ROUTINE)MyVoid

//然後線上程聲明為:

void MyVoid()

{

return;

}

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函式。

記憶體泄漏

其實,真正的原因並非如此。看如下一段代碼:

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 函式從一個進程裡面創建一個執行緒。這個開始的執行緒必須指定開始執行代碼的地址,新執行緒執行。有代表性的,開始地址就是一個函式名。這個函式有一個參數,並且返回一個 DWORD 值。一個進程裡面同時有多個執行緒在執行。

下面這個例子演示如何創建一個新執行緒,執行本地定義的函式。 ThreadProc. 建立的執行緒動態分配記憶體傳遞信息到每個執行緒的實例中。執行緒函式負責釋放這些記憶體。

被調用的執行緒用 WaitForMultipleObjects 持續等待,直到所有的工作執行緒退出。線上程退出後,關掉執行緒函式的句柄。

相關詞條

相關搜尋

熱門詞條

聯絡我們