Windows via C/C++ 学习(15)线程调度、线程优先级和亲缘性
每一個線程在它的線程內核對象中有一個上下文結構,反映了線程最后一次執行的 CPU 寄存器狀態,每隔大約 20ms(可以使用 GetSystemTimeAdjustment 獲得,我測得這個間隔大約為 15.6ms),Windows 在所有當前內核對象中查找可調度線程,并選擇一個可調度線程,從這個線程的上下文結構中讀取 CPU 寄存器狀態,這個動作叫做“上下文切換”。這時,這個線程開始執行它的代碼,大約 20ms 后,Windows 將 CPU 寄存器的狀態保存到這個線程的上下文結構中,這個線程不再執行,Windows 查找下一個可調度線程執行上下文切換,另一個線程繼續從它上一次中斷的地方開始執行,這個過程從系統開機到系統關閉周而復始。
某些線程不是可調度線程,因為這些線程可能被暫停執行,或者正在待某個任務完成。
由于 WIndows 系統是搶先式操作系統,意味著一個線程可能會在任何時候被其它線程中繼,又因為 Windows 系統不是實時操作系統,所以不能保證某一時刻某個線程一定會被調度執行,也不能保證這個線程一定會執行給定的時間片。
線程的暫停和恢復
在線程內核對象內部有一個表示暫停計數器的成員,當你調用 CreateProcess 或 CreateThread 時,這個計數器初始化為 1,避免線程被調度給 CPU,保證線程在完全初始化之前不會執行任何代碼,一旦線程完全初始完成,創建函數會檢測你是否傳遞了一個 CREATE_SUSPENDED 標志,如果沒有傳遞這個標志,函數會減少暫停計數器計數,只要計數器為 0,這個線程就成為可調度線程,否則它會暫停執行。
可以使用
DWORD ResumeThread(HANDLE hThread);來恢復線程的執行,這個函數如果成功,返回之前線程被暫停的次數,否則返回 0xFFFFFFFF。
一個線程可以被暫停多次,那么恢復的話也必須調用 ResumeThread 多次,任何線程都可以調用
DWORD SuspendThread(HANDLE hThread);來暫停一個線程的執行,只要它有那個線程的句柄,一個線程可以暫停自身的執行,但不可恢復自身。一個線程可以最多被暫停 MAXIMUM_SUSPEND_COUNT (WinNT.h 定義為 127 )次。暫停一個線程時必須非常注意,因為你不知道將要被暫停的線程正在做什么,比如,如果一個線程正在從堆中申請內存,這時,這個線程會在堆中有一個鎖,如果這時線程被暫停執行,其它線程會一直待鎖被釋放,這樣會造成死鎖。
暫停和恢復一個進程
Windows 并沒有提供一個暫停和恢復一個進程內所有線程的函數,我們可以枚舉一個進程內的所有線程來達到這個目的,下面是一段摘自 Windows via C/C++ 的一段代碼:
VOID SuspendProcess(DWORD dwProcessId, BOOL fSuspend) {HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, dwProcessId);if(hSnapshot != INVALID_HANDLE_VALUE){THREADENTRY32 te = {sizeof(THREADENTRY32)};BOOL fOk = Thread32First(hSnapshot, &te);for (; fOk; fOk = Thread32Next(hSnapshot, &te)){// 當前線程屬于進程嗎?if(te.th32OwnerProcessID == dwProcessId){// 將線程ID轉換為一個句柄HANDLE hThread = OpenThread(THREAD_SUSPEND_RESUME,FALSE, te.th32OwnerProcessID);if(hThread != NULL){// 暫停或恢復這個線程if(fSuspend)SuspendThread(hThread);elseResumeThread(hThread);}CloseHandle(hThread);}}CloseHandle(hSnapshot);} }但是這段代碼可能不會 100% 有效,如果在調用 CreateToolhelp32Snapshot 后目標進程創建了一個新線程,那么這個線程不會被枚舉到,新線程也就不會被暫停;更糟糕的是,在枚舉一個線程的過程中一個目標進程的線程可能被釋放另一個線程正在被創建,新線程的 ID 可能與釋放的線程 ID 相同但可能不屬于同一個進程,這樣有可能造成暫停了另一個進程的線程。
睡眠
一個線程可以調用
VOID Sleep(DWORD dwMilliseconds);來使自己睡眠一段時間,這段時間內線程不可調度。
調用這個函數會使線程自動放棄已分配給它的剩余時間片,睡眠的時間也是一個近似值,如果傳遞一個 INFINITE 參數,表示線程會一直睡眠,直到進程終止,一般情況下很少這樣用,但一個永不喚醒的線程可以保留它的線程棧和線程內核對象,其它線程可以使用它們,如果傳遞一個 0 參數,它告訴線程放棄剩余的時間片,強制系統調度另一個線程,可是如果沒有其它線程可調度的話,這個線程可能會立即被重新調度執行。
CONTEXT 結構
CONTEXT 結構允許系統記住線程的狀態,線程下一次得到 CPU 時間的時候它可以繼續執行。這個結構在 WinNT.h 中定義,它的每一個成員對應一個 CPU 寄存器。
CONTEXT 結構有幾個部分,CONTEXT_CONTROL 包含了 CPU 的控制寄存器,如指令指針、棧指針、標志寄存器、函數返回地址等;CONTEXT_INTEGER 表示 CPU 的整數寄存器;CONTEXT_FLOATING_POINT 表示 CPU 的浮點指針寄存器;CONTEXT_SEGMENTS 表示 CPU 的段寄存器;CONTEXT_DEBUG_RGEISTERS 表示 CPU 的調試寄存器;CONTEXT_EXTENDED_REGISTERS 表示 CPU 的擴展寄存器。
可以使用
BOOL GetThreadContext(HANDLE hThread,PCONTEXT pContext);來獲取 CONTEXT 結構,調用這個函數之前,你必須為 CONTEXT 分配內存空間,并且設置 ContextFlags 來確定你期望得到哪些寄存狀態,你可以在調用 GetThreadContext 之前調用 SuspendThread 來暫停線程的執行,否則你可能得不到指定點的寄存器值,由于 SuspendThread 函數只會暫停用戶模式下代碼的執行,不會對內核模式代碼有影響,所以可以放心地調用 GetThreadContext,它會在內核模式下繼續運行來獲得期望的結果。
可以使用
BOOL SetThreadContext(HANDLE hThread,CONST CONTEXT *pContext);來設置寄存器的內容,這是一個將線程搞崩潰的好方法。
線程優先級
Windows 系統有 32 種優先級,分別是 0(最低)到 31(最高)。Windows 是一種搶先式操作系統,意味著高優先級的線程會搶先低優先級的線程的執行,只要有高優先級的線程正在執行,低優先級的線程就不會有機會被調度。
Windows 系統并不直接對線程設置優先級,而是使用進程優先級類和線程相對優先級來設置一個線程的優先級。有多各種方法設置優先級類,可以使用 CreateProcess 創建子進程時傳遞給 fdwCreate 一個表示優先級類的標識符進行設置,還可以通過
BOOL SetPriorityClass(HANDLE hProcess,DWORD fdwPriority);設置指定進程的優先級類,第三種方式是使用 start 命令行并指定的一個表示優先級選項來啟動一個進程,還可以在任務管理器中設置一個進程的優先級。有 6 種進程優先級類,分別是
| 優先級類 | 標識符 |
| 實時 | REALTIME_PRIORITY_CLASS |
| 高 | HIGH_PRIORITY_CLASS |
| 高于默認 | ABOVE_NORMAL_PRIORITY_CLASS |
| 默認 | NROMAL_PRIORITY_CLASS |
| 低于默認 | BELOW_NORMAL_PRIORITY_CLASS |
| 空閑 | IDLE_PRIORITY_CLASS |
不能在 CreateThread 時設置新線程的相對優先級,只能通過
BOOL SetThreadPriority(HANDLE hThread,int nPriority);設置一個指定線程的優先級,有 7 個相對優先級,分別是
| 相對優先級 | 常量標識符 |
| 時間關鍵 | THREAD_PRIORITY_TIME_CRITICAL |
| 最高 | THREAD_PRIORITY_HIGHEST |
| 高于默認 | THREAD_PRIORITY_ABOVE_NORMAL |
| 默認 | THREAD_PRIORITY_NORMAL |
| 低于默認 | THREAD_PRIORITY_BELOW_NORMAL |
| 最低 | THREAD_PRIORITY_LOWEST |
| 空閑 | THREAD_PRIORITY_IDLE |
線程優先級的動態提升
通常系統在處理一些 I/O 事件或磁盤讀取時會動態提升相應線程的優先級,例如,用戶按下一個按鍵,系統會將一個 WM_KEYDOWN 消息放入線程的消息隊列中,鍵盤驅動程序會告訴系統臨時提升線程的優先級來處理這個消息,默認情況提升 2 個級別,在第二個時間片,它的優先級降低 1,第三個時間片降到平常水平。系統僅對 1 到 15 之間的優先級做這種提升,這之間的優先級叫做動態優先級范圍,系統不會對高于 15 級以上的線程做動態提升,另外,這個提升是由驅動程序告訴系統的。可以使用
BOOL SetProcessPriorityBoost(HANDLE hProcess,BOOL bDisablePriorityBoost); BOOL SetThreadPriorityBoost(HANDLE hThread,BOOL bDisablePriorityBoost);分別設置是否將相應進程或相應線程進行動態提升,使用
BOOL GetProcessPriorityBoost(HANDLE hProcess,PBOOL pbDisablePriorityBoost); BOOL GetThreadPriorityBoost(HANDLE hThread,PBOOL pbDisablePriorityBoost);來獲得相應進程或線程是否禁用了動態提升。
另外,如果一個低優先級的線程已準備好執行,可是因為高優先級的線程正在一直執行,這種情況如果持續一段時間,一般是 3 到 4 秒鐘,系統會臨時將低優先級的線程的優先級提高到 15,并運行兩個時間片的時間,然后再將的優先級降到平常水平,這樣能保證低優先級的線程能得到執行。
I/O 請求優先級調度
當一個線程正在進行長時間的 I/O 請求時,系統因為慢速的 I/O 操作導致響應不暢,解決這個問題的辦法是在開始 I/O 操作之前傳遞 THREAD_MODE_BACKGROUND_BEGIN 標志給 SetThreadPriority,告訴系統降低當前線程的優先級,在 I/O 完成之后傳遞 THREAD_MODE_BACKGROUND_END 標志給 SetThreadPriority 將優先級恢復到之前的狀態;還可以使用 SetProcessClass 分別傳遞 PROCESS_MODE_BACKGROUND_BEGIN 和 PROCESS_MODE_BACKGROUND_END 來降低或恢復當前進程中所有線程的優先級,注意,只能對當前進程或線程進行這樣的操作,不允許這樣改變其它進程和其它線程的優先級。
轉載于:https://www.cnblogs.com/Fly-pig/archive/2011/02/11/1947290.html
總結
以上是生活随笔為你收集整理的Windows via C/C++ 学习(15)线程调度、线程优先级和亲缘性的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Oracle WebCenter 11g
- 下一篇: s3c2440移植MQTT