l I/O完成連接埠對線程池的管理
現在應該說說I/O完成連接埠為什麼這麼有用。首先,在建立I/O完成連接埠時,指定了能夠並發啟動並執行線程數量。如前所述,通常這個值為主機的CPU數量,如雙CPU的機器。在產生已完成的I/O請求隊列時,I/O完成連接埠將喚醒等待的線程。但是,完成連接埠最多僅會喚醒指定數量的線程。因此,如果有四個I/O請求完成了,並且有四個調用了GetQueuedCompletionStatus的線程正在等待,I/O完成連接埠僅僅會喚醒兩個線程;其他兩個線程繼續休眠。被喚醒的線程處理完I/O項後,重又調用GetQueuedCompletionStatus。系統看到隊列中還有更多的項,於是重新喚醒同一個線程進行處理。
如果仔細的思考一下,會注意到某些東西沒有太大的意義:如果完成連接埠僅允許指定數量的線程被並發的喚醒,為什麼還要多餘的線程線上程池中等待?比如,在一個雙CPU的機器上建立I/O完成連接埠,告訴它只允許有不超過2個的線程同時處理完成的I/O項。然後線上程池內建立4個線程(CPU數量乘以2)。結果就是看來好象建立了2個多餘的,永不會被喚醒線程。
其實I/O完成連接埠沒有那麼笨。在完成連接埠喚醒某個線程時,會將線程ID放到與完成連接埠關聯的第四個資料結構中:釋放的線程列表 (參考表2-1)。這樣完成連接埠會記住喚醒了哪些線程,並監視這些線程的執行情況。如果被釋放的線程調用了任何函數將自己置為等待狀態,完成連接埠會檢測到並更新內部資料結構:將線程ID從釋放的線程列表中移動到被暫停線程列表中(第五個也是最後一個與I/O完成連接埠有關的資料結構)。
完成連接埠的目標是將保持可能多的――和建立完成連接埠時所指定的並發線程數量一樣多的――線程處於被釋放的線程隊列中。如果一個被釋放的線程出於無論任何原因又進入等待狀態,被釋放的線程隊列會進行收縮,完成連接埠會釋放另一個正在等待的線程。如果被暫停線程醒來,它會離開暫停隊列,重新進入被釋放的線程列表。這表示被釋放的線程列表可以擁有超過所允許的最大並發數量的項。
注意
一旦線程調用GetQueuedCompletionStatus,會被安排到指定的完成連接埠。系統假定所有被安排的線程會根據被指定的完成連接埠進行工作。完成連接埠僅在那些正在啟動並執行被安排的線程數量少於完成連接埠的最大並發值時從線程池中喚醒線程。
可以通過以下三個方法之一來打斷線程/完成連接埠的安排。
1)使線程退出
2)使線程調用GetQueuedCompletionStatus,並傳入另一個不同的I/O完成連接埠
3)銷毀線程當前被安排的I/O完成連接埠
現在來個總結。假設在一個雙CPU的機器上,建立一個允許不超過2個線程被並發喚醒的完成連接埠,然後建立4個線程等待已完成的I/O請求。如果連接埠上產生了3個已完成的I/O請求,僅有兩個線程被喚醒來處理這些請求,以減少可啟動並執行線程數量和節省切換時間。現在如果一個正在啟動並執行線程調用Sleep, WaitForSingleObject, WaitForMultipleObjects, SingalObjectAndWait,同步的I/O調用,或者任何其他將導致線程離開運行態的函數,將會被I/O完成連接埠檢測到,並立即喚醒第三個線程。完成連接埠的目標就是保持CPU始終處於工作狀態。
最後,第三個線程將再次成為就緒態。此時,可啟動並執行線程數量將高於系統的CPU個數。但是,完成連接埠會意識到這點並且不再允許更多的線程被喚醒,直到可啟動並執行線程數量少於CPU個數。I/O完成連接埠架構假定可啟動並執行線程數量僅會在短時間內超過所指定的最大數量,並且會線上程進行下一迴圈或再次調用GetQueuedCompletionStatus時很快回落。這樣就解釋了線程池內會有多於完成連接埠所設定數量的線程存在。