线程的调度
概述
Windows?實現了一個由優先級驅動,搶占式的調度系統,也就是最高優先級的可運行的(就緒狀態下的)線程總是先運行。
有一種現象稱之為“處理器親合(processor affinity)”,即線程可能受處理器限制,只運行在那些允許它運行的處理器上。缺省的設置是線程可運行在任何可用的處理器上,用戶可以通過使用一個Win32的調度函數修改處理器的“親合性”。
當一個線程被選擇運行,它所運行的時間稱之為“時間片”。Windows中斷這個線程,去查找是否有別的同優先級或更高優先級的線程正在等待執行,或者這個線程的優先級需要被降低,在這之前這個線程運行的時間長度就是一個“時間片”。不同的線程,其時間片的值可以不同,Window 2000專業版和服務器版的時間片的值是不同的。然而,因為Windows實現的是一種搶占式的調度,一個線程可能未完成其時間片。如果另外有一個更高優先級的線程就緒,正在運行的這個線程就可能在未完成其時間片前被搶占。事實上,一個線程甚至會在未開始其時間片前就被搶占了,而要等待下一次被選擇運行。
Windows的沒有單獨的調度模塊或程序,調度的代碼是在內核中實現的,廣泛分布在內核中那些與調度相關的事件發生的地方。這些負責調度的程序被總稱為“內核的調度器”。線程調度發生在DPC/Dispatch級別。
以下這些事件發生時會觸發線程調度:
- 變成就緒狀態的線程。例如:一個新創建的線程,或者從等待狀態釋放出來的線程。
- 因其時間片結束而離開運行狀態的線程,它或者結束了,或者進入等待狀態。
- 線程的優先級改變了,是因為系統調用,或者是Windows自己改變了優先級。
- 正在運行的線程的處理器親合性改變了。
在每一個上述情況的銜接點,Windows必須決定下一個運行的線程是哪一個。一旦選擇了一個新的線程運行,Windows將對其執行一個上下文轉換的操作,即保存正在運行的線程的相關的機器狀態,裝載另一個線程的狀態,開始新線程的執行。
Windows的調度是以線程為粒度調度的。調度的決策被嚴格限制在以線程為基礎,并不考慮這個線程屬于哪一個進程。當考慮到進程并不運行,而僅為其線程提供資源和運行的上下文環境時,這種方法就有意義了,例如,進程A有10個可運行的線程,進程B有2個可運行的線程,而且這12個線程的優先級別相同,那么,每一個線程將會使用1/12的CPU時間,而不是將CPU 50%的時間分配給進程A,50%?的時間分配給進程B。
為了明白線程調度算法,必須首先明白Windows所使用的優先級別。
線程優先級別
如Figure 6-12圖示:Windows內部使用32個優先級別,從0-31。這些數值被分成以下幾類:
- 16個實時級別(16-31)
- 15個變化的級別(1-15)
- 1個系統級別(0),?被保留用作0頁線程
線程優先級別是從兩個不同的方面來分配的:一個是從Win32應用程序編程接口,另一個是從Windows的內核。
Win32 API的進程在創建時所分配的優先級包括:Real-time, High, Above Normal, Normal, Below Normal, and Idle,進程中各個線程的相關優先級包括:Time-critical, Highest, Above-normal, Normal, Below-normal, Lowest, and Idle。 應用程序默認的優先級為Normal。
運行在內核模式的線程可以被用戶模式的線程搶占掉,這與線程的狀態無關,優先級是決定性因素。
在Win32 API中,每個線程的優先級都是它所屬的進程的優先級和自己相關的線程優先級二者的組合。從Win32優先級映射到Windows內部數字式的優先級如Figure 6-13圖示:
線程的實時優先級
在動態范圍內,用戶可以可以升高或降低應用程序中線程的優先級。但是,如果要將進程升高到實時范圍內,就必須擁有升高調度優先級的權力。如果沒有這個權力而企圖將一個進程升高到實時優先級,操作不會失敗,只是高級級別(High class)將被使用。
很多Windows的重要的內核模式的系統線程是在實時優先級范圍內的,如果用戶進程花費了過多的時間運行在這個范圍內,可能會阻礙了重要的系統功能,如內存管理器、緩沖管理器、本地和網絡文件系統,甚至是一些設備驅動程序。因為硬件中斷擁有比任何線程都高的優先級,所以不會被阻礙。
在實時范圍內的線程性能上有一點不同,當它被搶占時,其線程時間量會被重新設置。
雖然Windows有一套優先級稱之為“實時”,但它們并不是通常意義上定義的實時。因為Windows并沒有提供真正的實時操作系統功能,例如確保中斷時間間隔,或者是讓線程得到一個確保的執行時間。
時間片(quantum)
前面已經提到,“時間片”就是在Windows檢查是否有另外一個同優先級的線程要執行前,線程運行的時間。如果一個線程結束了它的時間片,而又沒有另外一個同優先級的線程,Windows重新調度這個線程運行另外一個時間片。每個線程都有一個時間片的數值,它代表了線程可以運行多長時間,直到時間片屆滿。這個數值不是一個時間的計時長度,而是一個整數,我們稱之為“時間片單位(quantum units)”。
缺省的情況下,Windows 2000/XP Professional線程運行2個時鐘周期(6個時間片單位),Windows Server版線程運行12個時鐘周期(36個時間片單位)。每次時鐘中斷,時鐘中斷程序從線程時間片中扣除一個固定的值3(1個時鐘周期)。
Windows Server版設置了較長的缺省值是為了減少上下文轉換。當有客戶請求到來時,服務器應用程序被喚醒后,如果有較長的時間片,它就可以更好的完成這個請求,然后在時間片結束前返回等待狀態。
如果線程時間片沒有剩余,時間片結束處理進程就會被觸發,然后另外一個線程可能被選擇執行。當時鐘中斷發生時,如果系統正處于DPC/Dispatch級別或者更高,例如正在執行一個DPC或者一個中斷服務程序,在這種情況下,就算當前的線程在整個時鐘中斷間隔都沒有運行,它的時間片仍然會被減少。如果不是這樣做,而設備中斷或者DPC又總是剛好發生在時鐘間隔中斷前,線程的時間片可能因此總不減少。
不同的硬件平臺,時鐘間隔的長度是不同的。時鐘中斷的頻率是由HAL(硬件層)負責,而不是內核負責。例如,大部分x86單處理器的時鐘間隔是10毫秒,大部分x86多處理器的時鐘間隔是15毫秒。
每個時鐘滴答表示3個時間片單位,而不是一個時間片單位,這種表示方法是為了線程在等待完成的時候,其時間片可以被減少一部分。基本優先級少于14的線程執行了等待的函數,如WaitForSingleObject?或者WaitForMultipleObjects后,它的時間片就減少1個單位;優先級是14或更高的線程等待后,其時間片會被重新設置。
線程的時間片可以部分被減少的原因是,當線程在時鐘間隔計數器激發前進入等待狀態,如果沒有對其時間片進行調整,那么有可能這個線程的時間片就從不減少。例如:一個線程運行,然后進入等待狀態,然后再運行,再進入等待狀態。但當時鐘間隔計數器激發時,卻從來不是當前運行的線程,那么當它運行時其時間片就從不被記賬,也就不會減少。
線程的調度方案
線程可以處于的不同的執行狀態,圖6-14顯示了是Windows2000/XP中線程的狀態的互相轉變。
線程的狀態有以下幾種:
| 線程狀態 | 說明 |
| Ready(就緒) | 此狀態下的線程正在等待執行,當調度程序需要找一個線程來執行時,它僅考慮就緒狀態下的線程池。 |
| Standby(備用) | 已經被選中(當前活動線程的后繼),當條件合適時,調度程序對這個線程執行一個上下文轉換,備用線程將被切換到某個特定的處理器上運行。對于系統中的每一個處理器,只能有一個線程處于備用狀態。 |
| Running(運行) | 一旦調度程序將環境切換到某個(備用)線程,這個線程就進入運行狀態并開始執行。線程一直執行,直到內核將其搶占去運行一個更高優先級的線程,或者它的時間片到結束運行或自動進入等待狀態。 |
| Waiting(等待) | 一個線程可能因為以下幾個原因而進入等待狀態:(1)自動等待一個對象以便同步它的執行。(2)操作系統可以代替該進程進入等待(如為了解決換頁I/O)。(3)環境子系統引導線程掛起。 線程等待狀態結束后,根據其優先級,開始執行,或者進入就緒狀態。 |
| Transition?(轉變) | 當一個線程已經準備好執行,但它的內核棧被換出了內存,這時線程就進入轉變狀態。一旦它的內核棧被換入內存,線程就進入就緒狀態。 |
| Terminated?(終止) | 當一個線程完成執行,它就進入終止狀態。終止后,線程對象可能被刪除,也可能不被刪除,這將取決于對象管理器什么時候刪除對象的策略。如果執行體中有一個指針指向線程對象,執行體可以對線程對象重新初始化并再次使用它。 |
| Initialized?(初始) | 當一個線程被創建時的狀態。(內部使用) |
Windows在線程優先級上是以“誰將得到CPU”為基準的,但這個方法是實際上如何工作的呢?下面的部分將解釋在線程的級別上,由優先級驅動的,搶占式的多任務的調度是如何工作的。注意到Windows在處理線程調度決策上,單處理器系統和多處理器系統是不同的,這將在后續部分解釋。
(1)自愿切換
線程可能調用Win32的某些阻塞函數如WaitForSingleObject、?WaitForMultipleObjects來等待某個對象(如事件、信號量、I/O完成的端口、進程、線程、窗口信息等),從而進入等待狀態,自動放棄對CPU的占用。該線程進入同優先級就緒隊列的末尾,而CPU將上下文切換到就緒隊列中的下一個線程并開始執行。
以下就餐的情景能很好的幫助你理解線程的自動切換:在餐廳,你點了一個尚未準備好的漢堡包,為了不阻礙其他的就餐者,你就文明地站到一邊,讓下一個食客點菜。這時候,你的漢堡包正在準備中。漢堡包準備好之前,你站到了其優先級的就緒隊列的尾部。這樣似乎有些不公平,因為你先點的,所以當漢堡包準備好了的時候,服務員一般會先給你端過來。
同樣在操作系統中,為兼顧公平,大部分線程等待的對象受信后,一般會使用一個臨時增強優先級,以讓線程可以馬上執行。
那么線程剩余的時間片又如何呢?當線程進入等待狀態時,時間片的值并不重新設置。實際上,前面已經解釋過,當線程的等待狀態結束時,它的時間片被減少了1個時間量單位,相當于1/3個時鐘間隔。而優先級等于或高于14的線程,等待狀態結束后,它們的時間量會被重新設置。
(2)搶占式調度
這種調度情況是指一個低優先級的線程被一個較高優先級的線程強搶占。有2個原因會導致這種情況的發生:
l?????????較高優先級的線程的等待狀態結束,也就是另外一個線程在等待的事件發生。
l?????????線程的優先級被提高或降低。
在上述的任何一個情況下,Windows都必須決定當前運行的線程是否仍然繼續運行,還是被一個更高優先級的線程搶占運行。
注意:用戶模式下的線程可以搶占內核模式下的線程。其實,線程運行在什么模式下并沒有關系。線程的優先級才是決定因素。
當線程被搶占,它被放到了它運行的優先級的就緒隊列的頭。如果是實時優先級的線程,它的時間量會被重置成一個完整的時間片。如果是動態范圍的優先級的線程,它再次運行時,就完成它上次剩余的時間片。
搶占就可以粗略地比喻你的漢堡包已經準備好時,突然總統走來要訂一個漢堡包。當總統在拿他的午餐時,并不要求你去隊尾,出于一種尊重,你只需站到一邊。一旦總統離開,你可以馬上獲得漢堡包,立即享用。
(3)時間片用完
當一個運行的線程使用完它的時間量,Windows必須決定是否要降低線程的優先級,和是否讓另外一個線程使用處理器。
如果線程的優先級被降低,Windows尋找一個更適合的線程進行調度。一個更適合的線程是指優先級比當前運行的線程的新的優先級更高的,在就緒隊列中的線程。如果線程的優先級沒有被降低,而且又有同樣優先級的線程在就緒隊列中,Windows將在隊列中選擇下一個線程,而將先前運行的線程放到了隊列的尾部,并賦予它一個新的時間片值,將其狀態從運行改為就緒。如果沒有另外一個同樣優先級的線程準備好運行,線程將繼續執行它下一個時間片。
(4)終止
當線程調用了ExitThread從主函數中返回,或者被TerminateThread殺掉,它都結束運行,運行狀態改為終止狀態。如果該線程對象沒有打開的句柄,就會從進程的線程隊列中被刪除,其相關的數據結構將會釋放并重新被分配。
線程優先級的提升
在五種情況下,Windows會提升線程當前優先級的值:
- 完成I/O操作時;
- 等待執行體事件或者信號量受信后;
- 前臺進程的線程完成等待操作后;
- 因為窗口行為,GUI線程被喚醒時;
- 當線程準備好運行,但卻一直不能運行。(CPU饑餓)
這些調整的目的是為了提高系統整體的吞吐量和響應能力,同時也為了解決潛在的不公平的調度現象。正象任意的調度算法一樣,這些調整并不是完美的,并不是所有的應用程序都會從中得到好處。Windows從不提升那些在實時范圍內(16-31)的線程的優先級。因此,在談到實時范圍內的線程時,調度通常是可預知的。
?
說明:
本文摘自《Windows Internals》第6章《進程、線程和作業》6.5《線程調度》
總結
- 上一篇: 线程的数据结构
- 下一篇: Win32多线程编程(1) — 基础概念