本系列意在記錄Windwos線程的相關知識點,包括線程基礎、線程調度、線程同步、TLS、線程池等。
進程與線程
理解線程是至關重要的,每個進程至少有一個線程,進程是線程的容器,線程才是真正的執行體,線程必然在某個進程的上下文中運行。進程擁有惰性,如果進程中所有的線程都已結束,那麼進程也就沒有存在的必要了。
一個進程由如下兩部分組成:1、一個進程地址空間;2、一個進程核心對象
一個線程由如下兩部分組成:1、一個線程棧;2、一個線程核心對象
線程的開銷要比進程少很多,所以在解決編程問題的時候盡量考慮在當前進程中建立線程而不是建立新的進程。然而,線程的切換需要消耗一定數量的CPU資源,因此,也不是說可以毫無顧忌的使用線程來處理問題。
線程生命週期
線程的建立
系統建立一個線程核心對象;
系統在當前進程的中預訂一塊線程棧空間,並調撥一些實體記憶體;
線程終止運行
釋放線程所擁有的所有使用者物件(不太理解)
線程結束代碼從STILL_ACTIVE變成真正的結束代碼
線程核心對象變為觸發狀態
如果線程是進程中最後一個活動線程,進程將終止
線程核心對象的使用計數減1
線程的建立和終止方法
無論你使用什麼程式設計語言,什麼類庫,在Windows平台下最終建立線程都應該有下面的API
HANDLE WINAPI CreateThread( __in_opt LPSECURITY_ATTRIBUTES lpThreadAttributes,//安全性描述元,一般傳入NULL,但其中的bInheritHandle標誌位說明線程核心對象是否允許子進程繼承 __in SIZE_T dwStackSize,//線程棧初始化大小,該值可以傳入0,系統會從/STACK連結選項和此值兩個中選一個較大的 __in LPTHREAD_START_ROUTINE lpStartAddress,//線程執行的初始函數的地址 __in_opt LPVOID lpParameter,//傳入初始函數的參數 __in DWORD dwCreationFlags,//指定線程是否能被立即調度(即是否立即執行),如果為CREATE_SUSPENDED,系統會在初始化後暫停線程的運行 __out_opt LPDWORD lpThreadId//線程ID,可以傳入NULL);
需要說明的是線程初始函數總是擁有如下函數簽名:
DWORD WINAPI ThreadProc( __in LPVOID lpParameter);
該函數返回建立好的線程核心對象的控制代碼。除非該控制代碼值將用作他用,否則應該立即調用CloseHandle來關閉控制代碼,當然關閉控制代碼不會終止線程的運行,但可以保證線程在退出後即使的釋放線程核心對象。
終止線程的方式有線程初始函數返回、線程自己調用ExitThread終止自己、外部線程調用TerminateThread、包含線程的進程終止運行。其中除了第一種方法,其他都是不推薦的方式,因為線程不正常退出不能保證資源的正確釋放
使用ExitThread還能保證系統銷毀線程的堆棧,但TerminateThread將無法做到,直到進程終止;
線程的初始化內幕
CreateThread導致系統建立一個線程核心對象,該對象的初始引用計數為2。線程正常退出將遞減一次,關閉線程控制代碼將遞減一次,引用計數為0時,作業系統會釋放改核心對象。暫停計數設定為1,結束代碼設定為STILL_ACTIVE,對象被設定為未觸發狀態。
系統從進程地址空間中分配線程棧,並在高位寫入pvParam和pfnStratAddr。
每個線程都有其自己的一組CPU寄存器,稱為線程上下文。上下文反映了線程上一次執行時,線程的CPU寄存器狀態。當線程被重新調度時,儲存在核心對象中的上下文將回寫到CPU寄存器,以恢複線程的最後狀態,這個過程稱為“環境切換”。其中最為重要的兩個寄存器是堆棧寄存器(SP)和指令寄存器(IP),SP指向pfnStartAddr在堆棧中的地址,IP指向RtlUserThreadStart函數(NTDLL.dll匯入)。Windows實際上提供了一個描述線程內容相關的結構CONTEXT,並且提供了如下兩個函數獲得和設定上下文:
BOOL WINAPI GetThreadContext( __in HANDLE hThread, __inout LPCONTEXT lpContext);BOOL WINAPI SetThreadContext( __in HANDLE hThread, __in const CONTEXT *lpContext);
CONTEXT結構可能是Windows平台上唯一一個跟CPU有關的結構,所以如果要設定該結構可能需要考慮不同CPU的情況,而且如果設定不當,很有可能導致災難性的後果。
線程完全初始化好之後,系統將檢查CreateThread函數中的dwCreationFlags,如果此標記不是CREATE_SUSPENDED,系統將把掛起計數遞減至0,以便處理器調度該線程。
Microsoft C/C++運行庫注意事項
Visual Studio附帶了4個C/C++運行庫用於開發,還有兩個面向.NET託管環境。現在所有的庫都支援多線程開發,已經沒有專門針對單線程開發的運行庫(C\C++運行庫的出現早於多任務作業系統,因此當時有支援單線程的運行庫):
| 庫名稱 |
描述 |
| LibCMt.lib |
庫的靜態連結發行版本 |
| LibCMtD.lib |
庫的靜態連結調試版本 |
| MSVCRt.lib |
匯入庫,用於動態連結MSVCRxxx.dll庫的發行版本 |
| MSVCRtD.lib |
匯入庫,用於動態連結MSVCRxxxD.dll庫的調試版本(預設) |
| MSVCMRt.lib |
匯入庫,使用者託管/機器碼混合 |
| MSVCURt.lib |
匯入庫,編譯成百分之百的純MSIL代碼 |
始終應該用運行庫的_beginthreadex來建立線程,以及用_endthreadex來代替ExitThread。
線程的掛起、恢複和睡眠
如上文所討論的,線程在建立的時候可以設定是否掛起。我們可以通過如下兩個函數來掛起和恢複線程
DWORD WINAPI SuspendThread( __in HANDLE hThread);DWORD WINAPI ResumeThread( __in HANDLE hThread);
調用SuspendThread將遞增線程核心對象的掛起計數,可以掛起多次,對應的也可以恢複多次。另外,沒有完美的“掛起進程”的函數,因為唯一的方法就是遍曆並掛起進程中的所有線程,然而在遍曆的過程中如果有新的線程建立了呢,亦或是剛遍曆的線程銷毀了呢。因此試圖“掛起進程”要十分小心,盡量避免。
使用下面的函數指定掛起當前線程一段時間
VOID WINAPI Sleep( __in DWORD dwMilliseconds);
勞動果實,轉載請註明出處:http://www.cnblogs.com/P_Chou/archive/2012/06/10/basic-of-thread.html