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