电竞比分网-中国电竞赛事及体育赛事平台

分享

《windows核心編程系列 》六談?wù)劸€程調(diào)度、優(yōu)先級和關(guān)聯(lián)性

 求真我 2014-04-20

http://blog.csdn.net/ithzhang/article/details/8046723轉(zhuǎn)載請注明出處!

線程調(diào)度、優(yōu)先級和關(guān)聯(lián)性 

    每個線程都有一個CONTEXT結(jié)構(gòu),保存在線程內(nèi)核對象中。大約每隔20ms windows就會查看所有當前存在的線程內(nèi)核對象。并在可調(diào)度的線程內(nèi)核對象中選擇一個,將其保存在CONTEXT結(jié)構(gòu)的值載入cpu寄存器。這被稱為上下文切換。大約又過20ms  windows將當前cpu寄存器存回內(nèi)核對象,線程被掛起。Windows再次檢查內(nèi)核對象,并在可調(diào)度的內(nèi)核對象中選擇一個進行調(diào)度。此過程不斷重復(fù)直到系統(tǒng)關(guān)閉。

 

    Windows被稱為搶占式多線程系統(tǒng),系統(tǒng)可以在任何時刻停止一個線程而另行調(diào)度另外一個線程。我們對此可以有一些控制,但是權(quán)限很小。我們無法保證線程總在運行或者獲得整個處理器。

 

     由于windows并不是實時操作系統(tǒng),我們無法保證在某一時間段一定在運行。

 

     一般情況下,系統(tǒng)中的可調(diào)度線程很少。因為它們都在等待某個事件的到來。比如notepad程序在等待用戶輸入的時候它就是不可調(diào)度的。它在等待鍵盤的輸入消息。當我們向notepad中輸入時也并不意味著notepad會立即獲得cpu時間。原因就是:windows并不是實時調(diào)度系統(tǒng)。

 

    線程內(nèi)核對象中有一個值表示掛起計數(shù)。調(diào)用CreateThread時系統(tǒng)創(chuàng)建線程內(nèi)核對象,并把掛起計數(shù)初始化為1。這樣cpu就不會調(diào)度它。在初始化之后,系統(tǒng)檢查是否有CREATE_SUSPEND標識傳入。如果有函數(shù)返回,進程仍保持掛起狀態(tài)。否則將掛起計數(shù)遞減為0,此時線程就可以被調(diào)度了。

 

    通過創(chuàng)建一個掛起的進程或線程我們可以在它們執(zhí)行任何代碼前改變它們的環(huán)境,比如將其添加到作業(yè)中或是改變優(yōu)先級。然后將它們設(shè)為可調(diào)度的。這可以通過調(diào)用ResumeThread函數(shù)。ResumeThread執(zhí)行成功將返回前一次的掛起計數(shù)。失敗則返回0xFFFFFFFF。

 

    一個線程可以被掛起多次。除了在創(chuàng)建時傳入CREATE_SUSPEND標識外,還可以調(diào)用SuspendThread函數(shù)。第一個參數(shù)為想要掛起的線程句柄。任何線程都可以掛起另一個線程。掛起n次的線程要想變?yōu)榭烧{(diào)度的必須調(diào)用ResumeThread()n次。

 

實際開發(fā)中,在調(diào)用SuspendThread時必須非常小心,因為我們無法知道線程此時在干什么。如果一個線程在分配堆中的內(nèi)存,線程將鎖定堆,其他想要分配堆的線程將被掛起。直到第一個線程分配完畢。如果第一個線程被掛起,將會出現(xiàn)死鎖的情況。

 

    由于進程不是cpu調(diào)度的單位,所以不存在掛起進程的概念。但是我們可以掛起進程中所有的線程。可以調(diào)用調(diào)試器函數(shù)WaitForDebugEvent函數(shù)。恢復(fù)時可以調(diào)用ContinueDebugEvent。

 

    除了被別人調(diào)用SuspendThread掛起外,線程也可以告訴系統(tǒng)在一段時間內(nèi),可以將自己掛起,不需要調(diào)度。這可以調(diào)用Sleep實現(xiàn)。

    

  1. void Sleep(DWORD dwMilliseconds);  


 

    參數(shù)表示線程自己掛起的時間。但是實際的掛起時間只是近似于所設(shè)定的參數(shù)。Windows并不是實時操作系統(tǒng),不能保證線程可以準時醒來。實際時間取決于系統(tǒng)中其他線程的運行情況。當為其傳入0時,表示主調(diào)線程主動放棄本次時間片的剩余部分。注意是本次。

 

    系統(tǒng)提供一個名為SwitchToThread函數(shù),如果存在另一個可調(diào)度的線程,那么系統(tǒng)將讓此線程運行。

    BOOL SwitchToThread();

    調(diào)用此函數(shù)時,系統(tǒng)查看是否存在急需cpu時間的饑餓線程。如沒有則函數(shù)返回。如果存在,SwitchToThread將調(diào)度該線程。它與Sleep(0)很相似。區(qū)別在于SwitchToThread允許執(zhí)行低優(yōu)先級線程。

 

    當我們需要計算線程執(zhí)行某項任務(wù)的我的時間時,很多人習慣使用GetTickCount64函數(shù)。

 

  1. ULONG start=GetTickCount64();  
  2.   
  3.   //do something.  
  4.   
  5. ULONG end=GetTickCount64();  
  6.   
  7.        


 

 此段代碼有個前提就是代碼執(zhí)行不會被中斷。但是在搶占式OS中,線程可以隨時被終止。執(zhí)行上述代碼的線程可能在執(zhí)行第一個函數(shù)后就被掛起。一段時間后再次被調(diào)度。這時候時間就不準確了。

 

    Windows提供了一個函數(shù)可以返回一個線程以獲得cpu時間:   

   

  1. BOOL GetThreadTime(  
  2.   
  3.         HANDLE hThread,  
  4.   
  5.         PFILETIME pftCreationTime,  
  6.   
  7.         PFILETIME pftExitTime,  
  8.   
  9.        PFILETIME pftKernelTime,  
  10.   
  11.        PFILETIME pftUserTime);  
  12.   
  13.       


 

   第一個參數(shù)為想獲得的線程句柄。

    第二個參數(shù)返回(線程創(chuàng)建時間-1601110:00)的秒數(shù)。單位是100ns

    第三個表示退出時間-1601110:00的秒數(shù)。單位是100ns。

    第四個表示線程執(zhí)行內(nèi)核模式下的時間的絕對值。單位是100ns

    第五個表示線程執(zhí)行用戶模式代碼的時間的絕對值。單位是100ns

    類似的,GetProcessTime可以返回進程中所有線程的時間之和。

    在進行高精度的計算時上述函數(shù)仍然不夠。此時windows提供了以下函數(shù):

   

  1. BOOL QueryPerformanceFrequency(LARGE_INTEGER *pliFrequency)  
  2.   
  3. BOOL  QueryPerformanceCounter(LARGE_INTEGER *pliCount);  
  4.   
  5.       


 

    這兩個函數(shù)假設(shè)正在執(zhí)行的線程不會被搶占。它們都是針對生命期很短的代碼塊。GetCPUFrequencyInMHZ可以獲得cpu頻率。

    在windows定義的所有數(shù)據(jù)結(jié)構(gòu)中,CONTEXT結(jié)構(gòu)是唯一一個依賴于cpu的。我們可以通過調(diào)用GetThreadContext來獲得當期cpu寄存器的狀態(tài)。

   

  1. BOOL GetThreadContext(  
  2.   
  3.        HANDLE pThread,  
  4.   
  5.        PCONTEXT pContext);  


 

   第二個參數(shù)是CONTEXT結(jié)構(gòu)指針。在分配CONTEXT結(jié)構(gòu)后,需要初始化ContextFlag標志,表示以表示要獲取哪些寄存器。函數(shù)執(zhí)行后CONTEXT對象中就填入我們請求的成員。ContextFlag可以是:

CONTEXT_CONTROL表示控制寄存器。

    CONTEXT_INTEGER表示整數(shù)寄存器。

    CONTEXT_FLOAT 表示浮點寄存器。

    CONTEXT_ALL 表示CONTEXT_CONTROL |CONTEXT_INTEGER|CONTEXT_SEGMENTS。

    在調(diào)用GetThreadContext時,需要先調(diào)用SuspendThread。因為在調(diào)用GetThreadContext時系統(tǒng)可能正在執(zhí)行那個線程,此時線程的上下文與獲得的信息就不一致了。注意,它只能返回線程的用戶模式上下文。如果當調(diào)用SuspendThread時線程正在內(nèi)核模式運行,線程不會暫停,直到其返回用戶空間。但是返回到用戶控件后不會執(zhí)行任何用戶模式代碼。

    不僅僅能獲得線程的進程上下文,我們還可以設(shè)置它。這可以調(diào)用:

   

  1. BOOL SetThreadContext()  
  2.   
  3.        HANDLE hThread,  
  4.   
  5.        CONST CONTEXT *pContext);  


 

    GetThreadContext和SetThreadContext函數(shù)為我們提供了對線程許多控制的方法,但是需要小心使用。

    線程優(yōu)先級

    前面提到的調(diào)度程序在調(diào)度另外一個線程之前,可以運行一個線程大約20ms的時間。但是這是所有優(yōu)先級都相同的情況。實際上系統(tǒng)中的很多線程優(yōu)先級是不同的,這將影響調(diào)度程序如何選擇下一個要運行的線程。

    Windows的線程優(yōu)先級從031。每個線程都會分配一個優(yōu)先級。當系統(tǒng)確定給哪個線程分配cpu時,它會首先查看優(yōu)先級為31的線程,直至所有優(yōu)先級為31的線程都被調(diào)度。然后再查看下一優(yōu)先級線程。只要存在優(yōu)先級為31的線程,系統(tǒng)就不會調(diào)度0-30級的線程。低優(yōu)先級線程長時間得不到cpu時間,這被稱為饑餓。這不經(jīng)常出現(xiàn),因為大多數(shù)線程都是不可調(diào)度的。

    系統(tǒng)啟動時會創(chuàng)建一個優(yōu)先級為0idle線程,整個系統(tǒng)只有它的優(yōu)先級為0。它在系統(tǒng)中沒有其他線程運行時將系統(tǒng)內(nèi)存中所有閑置頁面清0。

    Windows中的線程優(yōu)先級是由優(yōu)先級類和相對線程優(yōu)先級來確定的。系統(tǒng)通過線程的相對優(yōu)先級加上線程所屬進程的優(yōu)先級來確定線程的優(yōu)先級值。這個值被稱為線程的基本優(yōu)先級值。

    Windows支持6個進程優(yōu)先級類:idle ,below normal ,normal ,above normal,highreal-time。它們是相對與進程的。Normal最為常用,為99%的進程使用。

    idle優(yōu)先級類在系統(tǒng)什么都不做的時候運行的應(yīng)用程序。如屏幕保護程序。real-time優(yōu)先級類優(yōu)先級別最高,但是沒有開放給用戶使用。因為此優(yōu)先級類的程序會影響操作系統(tǒng)的任務(wù)。

    Windows支持7個相對線程優(yōu)先級:idle,lowest ,below normal,normalabove normal,highesttime-critical。這些優(yōu)先級是相對于進程優(yōu)先級的。大多數(shù)的線程使用normal優(yōu)先級。

    概括起來就是進程屬于某個優(yōu)先級類,另外還可以指定進程中線程的相對線程優(yōu)先級。也就是說線程優(yōu)先級是相對于進程優(yōu)先級的。time-critical優(yōu)先級對于real-time優(yōu)先級類,優(yōu)先級為31。相對于其他優(yōu)先級類則為15

    需要注意的是進程優(yōu)先級是抽象的概念,因為進程并不參與調(diào)度。

在優(yōu)先級編程時,首先需要在調(diào)用CreateProcess時可以再fdwCreate參數(shù)中傳入想要的優(yōu)先級。fdwCreate可以是以下標識符:

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

進程運行后可以調(diào)用SetPrioritClass來改變進程優(yōu)先級類。

    

  1. BOOL SetPriorityClass(  
  2.   
  3.          HANDLE hProcess,  
  4.   
  5.          DWORD fdwPriority);  
  6.   
  7.       


 

   可以調(diào)用GetPriorityClass來獲得進程的優(yōu)先級類。

    DWORD GetPriorityClass(HANDLE hProcess);

    上面是指定的進程優(yōu)先級類,調(diào)用CreateThread創(chuàng)建線程時,它的線程優(yōu)先級總是被設(shè)置為normal??梢哉{(diào)用以下函數(shù)來改變線程優(yōu)先級:

    

  1. BOOL SetThreadPriority(  
  2.   
  3.         HANDLE hThread,  
  4.   
  5.     int nPriority);  


 

  nPriority可以是以下標識符:

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時需要傳入CREATE_SUSPEND,使線程暫停執(zhí)行。

    相應(yīng)的可以調(diào)用int GetThreadPriority(HANDLE hThread);返回線程相對優(yōu)先級。

     Windows并沒有返回線程優(yōu)先級的函數(shù),而是分別提供返回進程優(yōu)先級類和相對線程優(yōu)先級。

    有些時候,系統(tǒng)也會提升一個線程的優(yōu)先級。比如某個線程正在等待用戶按鍵消息。當用戶敲了一個鍵,系統(tǒng)會在線程的消息隊列中放入一個WM_KEYDOWN消息。此時線程就變成可調(diào)度的了。鍵盤設(shè)備驅(qū)動程序?qū)⑹瓜到y(tǒng)臨時提升線程的優(yōu)先級。在該時間片結(jié)束后,系統(tǒng)會將線程的優(yōu)先級值減一,第三個時間片執(zhí)行時再減去一。直至保持基本優(yōu)先級運行。

    注意:線程的當前優(yōu)先級不會低于進程的基本優(yōu)先級。而且設(shè)備驅(qū)動程序可以決定動態(tài)提升的幅度。系統(tǒng)只提升優(yōu)先級值在1~15的線程。這個范圍被稱為動態(tài)優(yōu)先級范圍。可以通過調(diào)用以下函數(shù)來禁止系統(tǒng)對線程優(yōu)先級進行動態(tài) 提升:

    

  1. BOOL SetProcessPriorityBoost(  
  2.   
  3.        HANDLE hProcess,  
  4.   
  5.        BOOL bDisablePriorityBoost);  


 

    此函數(shù)禁止動態(tài)提升此進程內(nèi)的所有線程的優(yōu)先級。

    

  1. BOOL SetThreadPriorityBoost(  
  2.   
  3.        HANDLE hThread,  
  4.   
  5.        BOOL bDisablePriorityBoost);  


 

    此函數(shù)禁止動態(tài)提升某個線程的優(yōu)先級。

還有一種動態(tài)提升優(yōu)先級的情況:檢測到有饑餓情況出現(xiàn)時,也就是某個線程由于優(yōu)先級低,而長時間無法得到調(diào)度時。系統(tǒng)就會動態(tài)提升此線程的優(yōu)先級。系統(tǒng)允許它運行兩個時間片。兩個時間片結(jié)束之后立即恢復(fù)到基本優(yōu)先級。

用戶正在使用的窗口被稱為前臺窗口。這個進程就被稱為前臺進程。為了改進前臺進程的響應(yīng)性,windows會為前臺進程中的線程微調(diào)調(diào)度算法。是前臺進程的線程分配比一般情況下更多的時間片。

    關(guān)聯(lián)性

    默認情況下,windows在分配cpu時采用軟關(guān)聯(lián)的方式。也就是說在其他因素相同的情況下,系統(tǒng)使線程在上一次運行的處理器上運行。這有助于重用仍在處理器高速緩存中的數(shù)據(jù)。

系統(tǒng)在啟動時確定cpu數(shù)量。應(yīng)用程序可以通過調(diào)用GetSysInfo來查詢cpu的數(shù)量。如果需要限制一個進程的所有線程在某些cpu上運行,可以調(diào)用:

  1. BOOL SetProcessAffinityMask(  
  2.   
  3.          HANDLE hProcess,  
  4.   
  5.          DWORD_PTR dwProcessAffinityMask);  


 

第一個參數(shù)代表要設(shè)置的進程句柄。

第二參數(shù)是一個位掩碼。代表線程可以在哪些cpu上運行。

注意子進程將繼承父進程的關(guān)聯(lián)性。

    GetProcessAffinityMask返回進程的關(guān)聯(lián)掩碼。

    相應(yīng)的還可以設(shè)置某個線程只在一組cpu上運行:

    SetThreadAffinityMask。

    有時候強制一個線程只在某個特定的cpu上運行并不是什么好主意。Windows允許一個線程運行在一個cpu上,但如果需要,它將被移動到一個空閑的cpu上。

    要給線程設(shè)置一個理想的cpu,可以調(diào)用:

    

  1. DWORD SetThreadIdealProcessro(  
  2.   
  3.       HANDLE hThread  
  4.   
  5.       DWORD dwIdealProcessor);  


 

    dwIdealProcessor是一個031/63之間的整數(shù)。表示線程希望設(shè)置的cpu??梢詡魅?span style="font-family:Times New Roman">MAXIMUM_PROCESSOR值,表示沒有理想的cpu。

                                                  2012、928于山西大同

  

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多