學習《Windows程序設計》記錄
概念貼士:
1. 線程描述了進程內代碼的執行路徑。
2. _stdcall是新標准C/C++函數的調用方法。從底層來說,使用這種調用方法參數的進棧順序和標准C調用(_cdecl方法)是一樣的,但是_stdcall采用自動清棧的方式,而_cdecl采用的是手動清棧方式。
3. Windows規定,凡是由其負責調用的函數一律定義為_stdcall類型。ThreadProc是一個回調函數,即有Windows系統負責調用的函數,所以該函數應定義為_stdcall類型。另外,當沒有顯式說明時,函數默認的調用方法是_cdecl。
4. 創建新線程的函數是CreateThread,由這個函數創建的線程將在調用者的虛擬地址空間內執行。該函數執行成功後,將返回新建線程的線程句柄。
5. WaitForSingleObject函數用於等待指定的對象(hHandle)變成受信狀態。其中參數dwMilliseconds給出了以毫秒為單位的要等待時間。當其值為INFINITE時,表示要等待無限長時間。
PS: WaitForSingleObject(
hThread, //要等待對象的句柄
INFINITE //要等待的時間(以毫秒為單位)
);
6. 當發生以下情況時,WaitForSingleObject函數就會返回:
1)要等待的對象變成受信(signaled)狀態;
2)參數dwMilliseconds指定的時間已過去了。
PS:一個可執行對象有兩種狀態,未受信(nonsignaled)和受信(signaled)狀態。線程對象只有當線程運行結束時才達到受信狀態。
7. 當創建子進程時,如果為CreateProcess函數的bInheritHandles參數傳遞TRUE,那麼子進程就可以繼承父進程的可繼承句柄。
8. dwCreationFlags--創建標志。如果為0,表示線程被創建後立即開始執行,如果指定為CREATE_SUSPENDED標志,表示線程被創建後處於掛起狀態,即暫停狀態,知道使用ResumeThread函數顯式地啟動該線程為止。
9. 線程內核對象可以說是一個包含了線程狀態信息的數據結構。系統提供的管理線程的函數其實就是依靠訪問線程內核對象來實現管理的。
圖表: CONTEXT(上下文,即寄存器的狀態)
EAX
EBX
其他CPU寄存器
Usage Count 使用計數(2)
Suspend Count 暫停次數(1)
Exi Code 退出代碼(STILL_ACTIVE)
Signaled 是否受信(FALSE)
... ... ... ...
10. 線程上下文:每個線程都有著他自己的一組CPU寄存器,稱為線程的上下文。
11. 使用次數:Usage Count成員記錄了線程內核對象的使用次數,這個計數說明了此內核對象被打開的次數。只要線程沒有結束運行,那麼這個計數的值至少為1。在創建一個新的線程是,CreateThread函數返回了線程內核對象的句柄,相當於打開一次新創建的內核對象,這也會促使Usage Count的值加1。所以創建一個新的進程後,初始狀態下Usage Count的值為2。之後,只要有進程打開這個內核對象,就會使得Usage Count的值加1。由於OpenThread函數的調用會靈Usage Count的值加1,所以在用完它們返回的句柄後一定要用CloseHandle函數進行關閉。(不關閉句柄,會造成內存洩漏。當然線程所在的進程結束後,該進程占用的所有資源都會統一釋放。)線程函數一旦返回,線程的生命周期就到此為止。
12. 暫停次數:線程內核對象中的Suspend Count用於指明線程的暫停次數。創建線程的時候指定CREATE_SUSPENDED標志,就可以在線程有機會在執行任何代碼之前改變線程的運行環境。(如優先級。)ResumeThread函數(喚醒一個被掛起的線程)會減少線程的暫停次數。注意,一個線程可以被暫停若干次。如果一個線程被暫停了3次,它必須被喚醒3次才可以分配給一個CPU。任何線程都可以調用SuspendThread函數來暫停另一個線程的運行。該函數可以增加線程的暫停次數。
13. 退出代碼:成員Exit Code指定了線程的退出代碼,也可以說是線程函數的返回值。同時也可以用GetExitCodeThread函數來獲取線程的退出代碼。
14. 是否受信:成員Signaled指定了線程對象是否為“受信”狀態。線程在運行期間,Signaled的值永遠是FALSE,即“未受信”。只有當線程結束後,系統才會把Signaled的值置為TRUE。
15. 線程的終止:當線程正常終止時,會發生下列事件:
1)在線程函數中創建的所有C++對象將通過它們各自的析構函數被正確地銷毀。
2)該線程使用的堆棧將被釋放。
3)系統將線程內核對象中Exit Code(退出代碼)的值由STELL_ACTIVE設置為線程函數的返回值。
4)系統將遞減線程內核對象中Usage Code(使用計數)的值。
16. 終止線程的執行有四種方法。
1)線程函數自然退出。當函數執行到return語句返回時,Windows將終止線程的執行。建議使用這種方法終止線程的執行。
2)使用ExitThread函數來終止線程。該函數會中止當前線程的進行,促使系統釋放所有該線程使用的資源,但C/C++資源卻不能得到正確的清楚。這裡有一個有關析構函數的例子,就不展示了。
3)使用TerminateThread函數在一個線程中強制終止另一個線程的執行。注意,這是被強烈建議避免的函數。因為一旦執行這個函數,程序將無法預測目標線程在何處被終止,這造成目標線程可能根本沒有機會來做清除工作,如打開文件占用的內存等都不會被釋放。另外,使用該函數終止進程的時候,系統不會釋放線程使用的堆棧。
4)使用ExitProcess函數來終止進程,系統會自動結束進程中所有線程的運行。但是這種方法相當於對每一個線程使用了TerminateThread函數,所以同樣強烈建議避免。
PS:每一個線程都應該讓它正常退出,即由它的線程函數返回。通知線程退出有很多方法,如使用事件對象、設置全局變量等。
17. 線程的優先級:每個線程都要賦予一個優先級號,取值從0(最低)到31(最高)。
18. Windows支持6個優先級類:idle、below normal、normal、above normal、high和real-time。進程屬於一個優先級類,還可以為進程內的線程賦予一個相對線程優先級。線程剛被創建時,它的相對優先級總是被設置為normal。(表示解壓文件時我修改了進程優先級,沒感覺速度加快了啊。憂傷。)
19. SetThreadPriority(HANDLE hThread, int nPriority)函數用去設置線程優先級。參數中前者是目標線程句柄,後者定義了線程的優先級。(後面會有相關優先級例子的展示:PriorityDemo.)
20. 實際編程中改變線程優先級的常用方法:創建一個線程的時候,將CREATE_SUSPENDED標記傳給了CreateThread函數,這可以使得新線程處於暫停狀態。在將它的優先級設為需要的優先級。再調用ResumeThread函數恢復線程的運行。
21. WaitForMultipleObjects函數用於等待多個內核對象。(在實例PriorityDemo中最後有所展示。)
22. Windows Explorer進程中的線程就是在高優先級下運行的。(請不要聯想到IE浏覽器。這裡的Explorer是指Windows程序管理器或者文件資源管理器,可以說是圖形界面的核心。別問我為什麼知道這麼清楚,因為我關閉過這個進程。)
23. 在實際開發中,一般不直接使用Windows系統提供的CreateThread函數創建線程,而是使用C/C++運行期函數_beginthreadex。(注:VC默認的C/C++運行期庫並不支持_beginthreadex函數。囧。)相應地,C/C++運行期庫同樣提供了另一個用於結束當前線程運行的函數--_endthreadex函數,用於取代ExitThread函數。
代碼解釋:
1.ThreadDemo
PS:主線程首先創建了一個輔助線程,打印出輔助線程的ID號,然後等待輔助線程運行結束;輔助線程僅打印出幾行字符串,僅模擬真正的工作。
1 #include <stdio.h> 2 #include <windows.h> 3 4 // 線程函數 5 DWORD WINAPI ThreadProc(LPVOID lpParam) 6 { 7 int i = 0; 8 while(i < 20) 9 { 10 printf(" I am from a thread, count = %d \n", i++); 11 } 12 return 0; 13 } 14 15 int main(int argc, char* argv[]) 16 { 17 HANDLE hThread; 18 DWORD dwThreadId; 19 20 // 創建一個線程 21 hThread = ::CreateThread ( 22 NULL, // 默認安全屬性 23 NULL, // 默認堆棧大小 24 ThreadProc, // 線程入口地址(執行線程的函數) 25 NULL, // 傳給函數的參數 26 0, // 指定線程立即運行 27 &dwThreadId); // 返回線程的ID號 28 printf(" Now another thread has been created. ID = %d \n", dwThreadId); 29 30 // 等待新線程運行結束 31 ::WaitForSingleObject (hThread, INFINITE); 32 ::CloseHandle (hThread); 33 return 0; 34 }
2.PriorityDemo
PS:下列程序同時創建了兩個線程,一個線程的優先級是“空閒”,運行的時候不斷打印出"Idle Thread is running"字符串;另一個線程的優先級是“正常”,運行的時候不斷打印出“Normal Thread is running”字符創。
1 #include <stdio.h> 2 #include <windows.h> 3 4 DWORD WINAPI ThreadIdle(LPVOID lpParam) 5 { 6 int i = 0; 7 while(i++<10) 8 printf("Idle Thread is running \n"); 9 10 return 0; 11 } 12 13 DWORD WINAPI ThreadNormal(LPVOID lpParam) 14 { 15 int i = 0; 16 while(i++<10) 17 printf(" Normal Thread is running \n"); 18 19 return 0; 20 } 21 int main(int argc, char* argv[]) 22 { 23 DWORD dwThreadID; 24 HANDLE h[2]; 25 26 // 創建一個優先級為Idle的線程 27 h[0] = ::CreateThread(NULL, 0, ThreadIdle, NULL, 28 CREATE_SUSPENDED, &dwThreadID); 29 ::SetThreadPriority(h[0], THREAD_PRIORITY_IDLE); 30 ::ResumeThread(h[0]); 31 32 // 創建一個優先級為Normal的線程 33 h[1] = ::CreateThread(NULL, 0, ThreadNormal, NULL, 34 0, &dwThreadID); 35 36 // 等待兩個線程內核對象都變成受信狀態 37 ::WaitForMultipleObjects( 38 2, // DWORD nCount 要等待的內核對象的數量 39 h, // CONST HANDLE *lpHandles 句柄數組 40 TRUE, // BOOL bWaitAll 指定是否等待所有內核對象變成受信狀態 41 INFINITE); // DWORD dwMilliseconds 要等待的時間 42 43 ::CloseHandle(h[0]); 44 ::CloseHandle(h[1]); 45 46 return 0; 47 } 48 49 /* 50 HANDLE h[2]; 51 h[0] = hThread1; 52 h[1] = hThread2; 53 DWORD dw = ::WaitForMultipleObjects(2, h, FALSE, 5000); 54 switch(dw) 55 { 56 case WAIT_FAILED: 57 // 調用WaitForMultipleObjects函數失敗(句柄無效?) 58 break; 59 case WAIT_TIMEOUT: 60 // 在5秒內沒有一個內核對象受信 61 break; 62 case WAIT_OBJECT_0 + 0: 63 // 句柄h[0]對應的內核對象受信 64 break; 65 case WAIT_OBJECT_0 + 1: 66 // 句柄h[1]對應的內核對象受信 67 break; 68 } 69 */