每個(gè)線程都有一個(gè)CONTEXT結(jié)構(gòu),保存在線程內(nèi)核對象中。大約每隔20ms windows就會(huì)查看所有當(dāng)前存在的線程內(nèi)核對象。并在可調(diào)度的線程內(nèi)核對象中選擇一個(gè),將其保存在CONTEXT結(jié)構(gòu)的值載入cpu寄存器。這被稱為上下文切換。大約又過20ms windows將當(dāng)前cpu寄存器存回內(nèi)核對象,線程被掛起。Windows再次檢查內(nèi)核對象,并在可調(diào)度的內(nèi)核對象中選擇一個(gè)進(jìn)行調(diào)度。此過程不斷重復(fù)直到系統(tǒng)關(guān)閉。
Windows被稱為搶占式多線程系統(tǒng),系統(tǒng)可以在任何時(shí)刻停止一個(gè)線程而另行調(diào)度另外一個(gè)線程。我們對此可以有一些控制,但是權(quán)限很小。我們無法保證線程總在運(yùn)行或者獲得整個(gè)處理器。
由于windows并不是實(shí)時(shí)操作系統(tǒng),我們無法保證在某一時(shí)間段一定在運(yùn)行。
一般情況下,系統(tǒng)中的可調(diào)度線程很少。因?yàn)樗鼈兌荚诘却硞€(gè)事件的到來。比如notepad程序在等待用戶輸入的時(shí)候它就是不可調(diào)度的。它在等待鍵盤的輸入消息。當(dāng)我們向notepad中輸入時(shí)也并不意味著notepad會(huì)立即獲得cpu時(shí)間。原因就是:windows并不是實(shí)時(shí)調(diào)度系統(tǒng)。
線程內(nèi)核對象中有一個(gè)值表示掛起計(jì)數(shù)。調(diào)用CreateThread時(shí)系統(tǒng)創(chuàng)建線程內(nèi)核對象,并把掛起計(jì)數(shù)初始化為1。這樣cpu就不會(huì)調(diào)度它。在初始化之后,系統(tǒng)檢查是否有CREATE_SUSPEND標(biāo)識(shí)傳入。如果有函數(shù)返回,進(jìn)程仍保持掛起狀態(tài)。否則將掛起計(jì)數(shù)遞減為0,此時(shí)線程就可以被調(diào)度了。
通過創(chuàng)建一個(gè)掛起的進(jìn)程或線程我們可以在它們執(zhí)行任何代碼前改變它們的環(huán)境,比如將其添加到作業(yè)中或是改變優(yōu)先級。然后將它們設(shè)為可調(diào)度的。這可以通過調(diào)用ResumeThread函數(shù)。ResumeThread執(zhí)行成功將返回前一次的掛起計(jì)數(shù)。失敗則返回0xFFFFFFFF。
一個(gè)線程可以被掛起多次。除了在創(chuàng)建時(shí)傳入CREATE_SUSPEND標(biāo)識(shí)外,還可以調(diào)用SuspendThread函數(shù)。第一個(gè)參數(shù)為想要掛起的線程句柄。任何線程都可以掛起另一個(gè)線程。掛起n次的線程要想變?yōu)榭烧{(diào)度的必須調(diào)用ResumeThread()n次。
實(shí)際開發(fā)中,在調(diào)用SuspendThread時(shí)必須非常小心,因?yàn)槲覀儫o法知道線程此時(shí)在干什么。如果一個(gè)線程在分配堆中的內(nèi)存,線程將鎖定堆,其他想要分配堆的線程將被掛起。直到第一個(gè)線程分配完畢。如果第一個(gè)線程被掛起,將會(huì)出現(xiàn)死鎖的情況。
由于進(jìn)程不是cpu調(diào)度的單位,所以不存在掛起進(jìn)程的概念。但是我們可以掛起進(jìn)程中所有的線程。可以調(diào)用調(diào)試器函數(shù)WaitForDebugEvent函數(shù)?;謴?fù)時(shí)可以調(diào)用ContinueDebugEvent。
除了被別人調(diào)用SuspendThread掛起外,線程也可以告訴系統(tǒng)在一段時(shí)間內(nèi),可以將自己掛起,不需要調(diào)度。這可以調(diào)用Sleep實(shí)現(xiàn)。
參數(shù)表示線程自己掛起的時(shí)間。但是實(shí)際的掛起時(shí)間只是近似于所設(shè)定的參數(shù)。Windows并不是實(shí)時(shí)操作系統(tǒng),不能保證線程可以準(zhǔn)時(shí)醒來。實(shí)際時(shí)間取決于系統(tǒng)中其他線程的運(yùn)行情況。當(dāng)為其傳入0時(shí),表示主調(diào)線程主動(dòng)放棄本次時(shí)間片的剩余部分。注意是本次。
系統(tǒng)提供一個(gè)名為SwitchToThread函數(shù),如果存在另一個(gè)可調(diào)度的線程,那么系統(tǒng)將讓此線程運(yùn)行。
BOOL SwitchToThread();
調(diào)用此函數(shù)時(shí),系統(tǒng)查看是否存在急需cpu時(shí)間的饑餓線程。如沒有則函數(shù)返回。如果存在,SwitchToThread將調(diào)度該線程。它與Sleep(0)很相似。區(qū)別在于SwitchToThread允許執(zhí)行低優(yōu)先級線程。
當(dāng)我們需要計(jì)算線程執(zhí)行某項(xiàng)任務(wù)的我的時(shí)間時(shí),很多人習(xí)慣使用GetTickCount64函數(shù)。
此段代碼有個(gè)前提就是代碼執(zhí)行不會(huì)被中斷。但是在搶占式OS中,線程可以隨時(shí)被終止。執(zhí)行上述代碼的線程可能在執(zhí)行第一個(gè)函數(shù)后就被掛起。一段時(shí)間后再次被調(diào)度。這時(shí)候時(shí)間就不準(zhǔn)確了。
Windows提供了一個(gè)函數(shù)可以返回一個(gè)線程以獲得cpu時(shí)間:
第一個(gè)參數(shù)為想獲得的線程句柄。
第二個(gè)參數(shù)返回(線程創(chuàng)建時(shí)間-1601年1月1日0:00)的秒數(shù)。單位是100ns。
第三個(gè)表示退出時(shí)間-1601年1月1日0:00的秒數(shù)。單位是100ns。
第四個(gè)表示線程執(zhí)行內(nèi)核模式下的時(shí)間的絕對值。單位是100ns。
第五個(gè)表示線程執(zhí)行用戶模式代碼的時(shí)間的絕對值。單位是100ns。
類似的,GetProcessTime可以返回進(jìn)程中所有線程的時(shí)間之和。
在進(jìn)行高精度的計(jì)算時(shí)上述函數(shù)仍然不夠。此時(shí)windows提供了以下函數(shù):
這兩個(gè)函數(shù)假設(shè)正在執(zhí)行的線程不會(huì)被搶占。它們都是針對生命期很短的代碼塊。GetCPUFrequencyInMHZ可以獲得cpu頻率。
在windows定義的所有數(shù)據(jù)結(jié)構(gòu)中,CONTEXT結(jié)構(gòu)是唯一一個(gè)依賴于cpu的。我們可以通過調(diào)用GetThreadContext來獲得當(dāng)期cpu寄存器的狀態(tài)。
第二個(gè)參數(shù)是CONTEXT結(jié)構(gòu)指針。在分配CONTEXT結(jié)構(gòu)后,需要初始化ContextFlag標(biāo)志,表示以表示要獲取哪些寄存器。函數(shù)執(zhí)行后CONTEXT對象中就填入我們請求的成員。ContextFlag可以是:
CONTEXT_CONTROL表示控制寄存器。
CONTEXT_INTEGER表示整數(shù)寄存器。
CONTEXT_FLOAT 表示浮點(diǎn)寄存器。
CONTEXT_ALL 表示CONTEXT_CONTROL |CONTEXT_INTEGER|CONTEXT_SEGMENTS。
在調(diào)用GetThreadContext時(shí),需要先調(diào)用SuspendThread。因?yàn)樵谡{(diào)用GetThreadContext時(shí)系統(tǒng)可能正在執(zhí)行那個(gè)線程,此時(shí)線程的上下文與獲得的信息就不一致了。注意,它只能返回線程的用戶模式上下文。如果當(dāng)調(diào)用SuspendThread時(shí)線程正在內(nèi)核模式運(yùn)行,線程不會(huì)暫停,直到其返回用戶空間。但是返回到用戶控件后不會(huì)執(zhí)行任何用戶模式代碼。
不僅僅能獲得線程的進(jìn)程上下文,我們還可以設(shè)置它。這可以調(diào)用:
GetThreadContext和SetThreadContext函數(shù)為我們提供了對線程許多控制的方法,但是需要小心使用。
線程優(yōu)先級
前面提到的調(diào)度程序在調(diào)度另外一個(gè)線程之前,可以運(yùn)行一個(gè)線程大約20ms的時(shí)間。但是這是所有優(yōu)先級都相同的情況。實(shí)際上系統(tǒng)中的很多線程優(yōu)先級是不同的,這將影響調(diào)度程序如何選擇下一個(gè)要運(yùn)行的線程。
Windows的線程優(yōu)先級從0到31。每個(gè)線程都會(huì)分配一個(gè)優(yōu)先級。當(dāng)系統(tǒng)確定給哪個(gè)線程分配cpu時(shí),它會(huì)首先查看優(yōu)先級為31的線程,直至所有優(yōu)先級為31的線程都被調(diào)度。然后再查看下一優(yōu)先級線程。只要存在優(yōu)先級為31的線程,系統(tǒng)就不會(huì)調(diào)度0-30級的線程。低優(yōu)先級線程長時(shí)間得不到cpu時(shí)間,這被稱為饑餓。這不經(jīng)常出現(xiàn),因?yàn)榇蠖鄶?shù)線程都是不可調(diào)度的。
系統(tǒng)啟動(dòng)時(shí)會(huì)創(chuàng)建一個(gè)優(yōu)先級為0的idle線程,整個(gè)系統(tǒng)只有它的優(yōu)先級為0。它在系統(tǒng)中沒有其他線程運(yùn)行時(shí)將系統(tǒng)內(nèi)存中所有閑置頁面清0。
Windows中的線程優(yōu)先級是由優(yōu)先級類和相對線程優(yōu)先級來確定的。系統(tǒng)通過線程的相對優(yōu)先級加上線程所屬進(jìn)程的優(yōu)先級來確定線程的優(yōu)先級值。這個(gè)值被稱為線程的基本優(yōu)先級值。
Windows支持6個(gè)進(jìn)程優(yōu)先級類:idle ,below normal ,normal ,above normal,high和real-time。它們是相對與進(jìn)程的。Normal最為常用,為99%的進(jìn)程使用。
idle優(yōu)先級類在系統(tǒng)什么都不做的時(shí)候運(yùn)行的應(yīng)用程序。如屏幕保護(hù)程序。real-time優(yōu)先級類優(yōu)先級別最高,但是沒有開放給用戶使用。因?yàn)榇藘?yōu)先級類的程序會(huì)影響操作系統(tǒng)的任務(wù)。
Windows支持7個(gè)相對線程優(yōu)先級:idle,lowest ,below normal,normal,above normal,highest和time-critical。這些優(yōu)先級是相對于進(jìn)程優(yōu)先級的。大多數(shù)的線程使用normal優(yōu)先級。
概括起來就是進(jìn)程屬于某個(gè)優(yōu)先級類,另外還可以指定進(jìn)程中線程的相對線程優(yōu)先級。也就是說線程優(yōu)先級是相對于進(jìn)程優(yōu)先級的。time-critical優(yōu)先級對于real-time優(yōu)先級類,優(yōu)先級為31。相對于其他優(yōu)先級類則為15。
需要注意的是進(jìn)程優(yōu)先級是抽象的概念,因?yàn)檫M(jìn)程并不參與調(diào)度。
在優(yōu)先級編程時(shí),首先需要在調(diào)用CreateProcess時(shí)可以再fdwCreate參數(shù)中傳入想要的優(yōu)先級。fdwCreate可以是以下標(biāo)識(shí)符:
real-time REALTIME_PRIORITY_CLASS
high HIGH_PRIORITY_CLASS
above normal ABOVE_NORMAL_PRIORITY_CLASS
normal NORMAL_PRIORITY_CLASS
below_normal BELOW_NORMAL_PRIORITY_CLASS
idle IDLE_PRIORITY_CLASS
進(jìn)程運(yùn)行后可以調(diào)用SetPrioritClass來改變進(jìn)程優(yōu)先級類。
可以調(diào)用GetPriorityClass來獲得進(jìn)程的優(yōu)先級類。
DWORD GetPriorityClass(HANDLE hProcess);
上面是指定的進(jìn)程優(yōu)先級類,調(diào)用CreateThread創(chuàng)建線程時(shí),它的線程優(yōu)先級總是被設(shè)置為normal??梢哉{(diào)用以下函數(shù)來改變線程優(yōu)先級:
nPriority可以是以下標(biāo)識(shí)符:
time-critical THREAD_PRIORITY_TIME_CRITICAL
highest THREAD_PRIORITY_HIGHEST
above-normal THREAD_PRIORITY_ABOVE_NORMAL
normal THREAD_PRIORITY_NORMAL
below-normal THREAD_PRIORITY_BELOW_NORMAL
lowest THREAD_PRIORITY_LOWEST
idle THREAD_PRIORITY_IDLE
但是在調(diào)用CreateThread時(shí)需要傳入CREATE_SUSPEND,使線程暫停執(zhí)行。
相應(yīng)的可以調(diào)用int GetThreadPriority(HANDLE hThread);返回線程相對優(yōu)先級。
Windows并沒有返回線程優(yōu)先級的函數(shù),而是分別提供返回進(jìn)程優(yōu)先級類和相對線程優(yōu)先級。
有些時(shí)候,系統(tǒng)也會(huì)提升一個(gè)線程的優(yōu)先級。比如某個(gè)線程正在等待用戶按鍵消息。當(dāng)用戶敲了一個(gè)鍵,系統(tǒng)會(huì)在線程的消息隊(duì)列中放入一個(gè)WM_KEYDOWN消息。此時(shí)線程就變成可調(diào)度的了。鍵盤設(shè)備驅(qū)動(dòng)程序?qū)⑹瓜到y(tǒng)臨時(shí)提升線程的優(yōu)先級。在該時(shí)間片結(jié)束后,系統(tǒng)會(huì)將線程的優(yōu)先級值減一,第三個(gè)時(shí)間片執(zhí)行時(shí)再減去一。直至保持基本優(yōu)先級運(yùn)行。
注意:線程的當(dāng)前優(yōu)先級不會(huì)低于進(jìn)程的基本優(yōu)先級。而且設(shè)備驅(qū)動(dòng)程序可以決定動(dòng)態(tài)提升的幅度。系統(tǒng)只提升優(yōu)先級值在1~15的線程。這個(gè)范圍被稱為動(dòng)態(tài)優(yōu)先級范圍??梢酝ㄟ^調(diào)用以下函數(shù)來禁止系統(tǒng)對線程優(yōu)先級進(jìn)行動(dòng)態(tài) 提升:
此函數(shù)禁止動(dòng)態(tài)提升此進(jìn)程內(nèi)的所有線程的優(yōu)先級。
此函數(shù)禁止動(dòng)態(tài)提升某個(gè)線程的優(yōu)先級。
還有一種動(dòng)態(tài)提升優(yōu)先級的情況:檢測到有饑餓情況出現(xiàn)時(shí),也就是某個(gè)線程由于優(yōu)先級低,而長時(shí)間無法得到調(diào)度時(shí)。系統(tǒng)就會(huì)動(dòng)態(tài)提升此線程的優(yōu)先級。系統(tǒng)允許它運(yùn)行兩個(gè)時(shí)間片。兩個(gè)時(shí)間片結(jié)束之后立即恢復(fù)到基本優(yōu)先級。
用戶正在使用的窗口被稱為前臺(tái)窗口。這個(gè)進(jìn)程就被稱為前臺(tái)進(jìn)程。為了改進(jìn)前臺(tái)進(jìn)程的響應(yīng)性,windows會(huì)為前臺(tái)進(jìn)程中的線程微調(diào)調(diào)度算法。是前臺(tái)進(jìn)程的線程分配比一般情況下更多的時(shí)間片。
關(guān)聯(lián)性
默認(rèn)情況下,windows在分配cpu時(shí)采用軟關(guān)聯(lián)的方式。也就是說在其他因素相同的情況下,系統(tǒng)使線程在上一次運(yùn)行的處理器上運(yùn)行。這有助于重用仍在處理器高速緩存中的數(shù)據(jù)。
系統(tǒng)在啟動(dòng)時(shí)確定cpu數(shù)量。應(yīng)用程序可以通過調(diào)用GetSysInfo來查詢cpu的數(shù)量。如果需要限制一個(gè)進(jìn)程的所有線程在某些cpu上運(yùn)行,可以調(diào)用:
第一個(gè)參數(shù)代表要設(shè)置的進(jìn)程句柄。
第二參數(shù)是一個(gè)位掩碼。代表線程可以在哪些cpu上運(yùn)行。
注意子進(jìn)程將繼承父進(jìn)程的關(guān)聯(lián)性。
GetProcessAffinityMask返回進(jìn)程的關(guān)聯(lián)掩碼。
相應(yīng)的還可以設(shè)置某個(gè)線程只在一組cpu上運(yùn)行:
SetThreadAffinityMask。
有時(shí)候強(qiáng)制一個(gè)線程只在某個(gè)特定的cpu上運(yùn)行并不是什么好主意。Windows允許一個(gè)線程運(yùn)行在一個(gè)cpu上,但如果需要,它將被移動(dòng)到一個(gè)空閑的cpu上。
要給線程設(shè)置一個(gè)理想的cpu,可以調(diào)用:
dwIdealProcessor是一個(gè)0到31/63之間的整數(shù)。表示線程希望設(shè)置的cpu??梢詡魅?span style="font-family:Times New Roman">MAXIMUM_PROCESSOR值,表示沒有理想的cpu。
2012、9、28于山西大同
聯(lián)系客服