線程由兩部分組成:線程內核對象和線程堆棧。
每個進程至少有一個線程。當進程初始化時,系統就會創建一個主線程。該主線程隨C/C++運行期啓動代碼一道運行,啓動代碼會調用進入點函數(main、wmain、WinMain或wWinMain),知道進入點函數返回,啓動代碼會調用ExitProcess結束進程爲止。
你可以在主線程中創建輔助線程,這些輔助線程必須要有自己的進入點函數,其形式如下:
DWORD WINAPI ThreadFunctionName(PVOID pvPrame){};
在這個進入點函數中,你可以做想做的事情。當線程函數運行到結尾處時並返回,線程結束運行,釋放線程堆棧,線程內核對象引用計數減1,如果線程內核引用計數爲0,系統撤銷該線程內核對象。
- 主線程的進入點函數爲main、wmain、WinMain或wWinMain;輔助線程的進入點函數爲我們按上面的方式創建的函數。
- 進入點函數必須要有一個返回值,該返回值會作爲線程的退出代碼。主線程會將該退出代碼作爲進程的退出代碼
一、CreateThread函數
HANDLE CreateThread(
PSECURITY_ATTRIBUTES psa,
DWORD cbStack,
PTHREAD_START_ROUTINE pfnStartAddr,
PVOID pvParam,
DWORD fdwCreate,
PDWORD pdwThreadID);
調用CreateThread創建線程時,系統會創建一個線程內核對象,並且會在進程的地址空間中分配一個內存,作爲線程堆棧使用。
- 注意:如果Visual c++編譯器時,我們應該使用_beginthreadex函數來替代CreateThread函數,如果是其他編譯器,也應該用其他的CreateThread替代函數。
psa:用於設定線程內核對象的安全屬性。 - cbStack:用於設定線程可以將多少地址空間作爲它自己的堆棧。
- pfnStartAddr:線程的進入點函數,即線程函數。
- pvPararam:想傳遞給線程進入點函數的指針。
- fdwCreate:用於設定創建的線程的標誌。如果爲0,表示線程創建後立即執行;爲CREATE_SUSPENDED,表示線程創建後,將線程掛起。
- pdwThreadID:用於存放系統分配給線程的ID。
二、終止線程運行
終止線程可以使用下面方法:
1)線程函數返回。
2)調用ExitThread函數,線程將自行撤銷。
3)同一個進程或另一個進程調用TerminateThread函數。
4)包含線程的進程終止運行。
方法一、線程函數返回
這種方法是線程資源被正確釋放的唯一方法。線程返回,能夠確保以下事情:
- 線程函數中定義的所有C++對象都能夠通過它們的撤銷函數正確地撤銷;
- 操作系統將正確的釋放線程堆棧使用的內存;
- 系統將線程的退出代碼設置爲線程函數的返回值;
- 系統將遞減線程內核對象的引用計數。
方法二、調用ExitThread函數
這種方法強制線程終止線程運行。這種方法將導致操作系統清除該線程使用的所有操作系統資源。但是C++資源不會被釋放。
VOID ExitThread(DWORD dwExitCode);
其中dwExitCode參數用於告訴系統將線程的退出代碼設置爲什麼。
方法三、調用TerminateThread函數
BOOL TerminateThread(HANDLE hThread,DWORD dwExitCode);
這種方法在擁有線程的進程結束前,不會釋放該線程的堆棧。
方法四、結束擁有線程的進程
線程終止時都會發生的事情:
- 線程擁有的所有用戶對象均被釋放;
- 線程退出代碼從STILL_ATIVE該爲傳遞給ExitThread或TerminateThread函數的代碼;
- 線程內核對象變爲已通知狀態;
- 如果該線程時最後一個活動線程,那麼進程也被視爲已經終止運行;
- 線程內核對象引用計數減1.
可以通過函數GetExitCodeThread來獲取線程退出代碼:
BOOL GetExitCodehread(HANDLE hThread,PDWORD pdwExitCode);
三、線程的一些性質
線程內核對象的初始引用計數爲2,暫停計數爲1,退出代碼爲STILL_ACTIVE。
當線程內核對象創建完成後,系統會爲線程分配堆棧內存,並且首先將傳給線程函數的參數壓入堆棧中,然後將線程函數的地址也壓入堆棧中。每個線程都有自己的上下文(一組寄存器,寄存器的值保持在CONTEXT結構中,CONTEXT包含在線程內核對象中)。線程初始化後,CONTEXT結構的堆棧指針指向堆棧的pfnStartAddrj,指令指針指向BastThreadStart函數的地址。
線程初始化完成後,會檢查CreateThread函數是否設置了CREATE_SUSPENDED標誌,如果沒有傳遞,那麼會將暫停計數減1,暫停計數爲0後線程被調度在進程中。然後將CONTEXT結構中的值加載到實際的CPU寄存器中,這樣就能夠執行該線程了。對於新線程來說,其指令指針被置爲BaseThreadStart的地址,因此這個函數實際上是新線程執行的開始地方。它有兩個參數,這兩個參數由系統將其壓入堆棧(或者CPU寄存器,看參數的保存形式)中,而不是函數調用。
新線程執行BaseThreadStart函數時,發生的情況:
- 在線程函數中建立一個結構化異常處理幀,對系統的異常設置默認處理;
- 調用線程函數,會將參數傳遞個線程函數;
- 線程函數返回時,BaseThreadStart會調用ExitThread函數,並將線程函數的返回值傳遞給它,線程內核對象引用計數減1;
- 如果線程出現異常,就會由BaseThreadStart建立的結構化異常處理幀處理異常。
對於主線程來說,它初始化後指令指針被設置爲另一個未文檔化的函數BaseProcessStart。其形式如下:
BaseProcessStart沒有引用pvParam參數,當BaseProcessStart開始執行時,其會調用C/C++運行期啓動代碼,啓動代碼會初始化main、wmain、WinMain或wWinMain,然後調用它們。當這些進入點函數返回時,C/C++運行期啓動代碼會調用ExitProcess。
爲什麼要多線程版本的運行庫,那是因爲對於單線程版本的運行期庫來說,如果設計多線程應用程序,那麼可能夠造成單線程版本的運行庫中的變量和函數造成同步問題。因此在多線程版本的運行期庫中設計了一個tiddata結構來解決問題。在多線程版本的運行庫中應該使用_ beginthreadex來創建線程,因爲在_beginthreadex函數中會爲每個線程分配一個tiddata結構體,然後會調用CreateThread函數來創建線程(這是系統創建線程的唯一方法)。
不應該調用的兩個運行期函數:_beginthread和_endthread。
獲取線程和進程的僞句柄,這些句柄對引用計數沒有影響,運用他們調用CloseHandle將會返回FALSE:
HANDLE GetCurrentProcess();
HANDLE GetCurrentThread();
獲取線程和進程的獨一無二的ID:
DWORD GetCurrentProcessId();
DWORD GetCurrentThreadId();