完成埠高效的三個原因
最近看了點完成埠的內容,一點心得記錄如下:
1.避免了select的查詢,可以從socket直接定位到完成埠。想象同時上千個連線的程式中,別的模型裡只能通過select的方式對所有的socket連結查詢一次才能知道哪個socket上有事件;而完成埠模型中,一旦一個socket上有事件發生,它立即將事件組成一個完成包放入完成埠(實際上是個佇列,放入完成埠的內部API是KeInsertQueue),這時等待執行緒直接從中取出該事件,如此即避免了一次查詢。
2.相對一個連結一個執行緒的模型,避免了大量執行緒切換,能夠最大限度的利用CPU。
用一個執行緒管理多個連線的弊端顯而易見,不能同時響應多個客戶請求。那麼一個連線對應一個執行緒呢?在連線數較多的情況下,大量的CPU時間都花在了執行緒切換上,從而降低了CPU的利用率。完成埠模型則限定了同時執行的執行緒數儘量控制在(N*CPU)之內,N一般為1。這樣做的好處是減少了執行緒切換,提高了CPU的利用率。實際上完成埠是在管理一個執行緒池,它會記錄當前活動的(即沒有被I/O等事件阻塞)執行緒數,一旦活動執行緒數低於N*CPU個,將喚醒池內的其他執行緒。並且喚醒次序是LIFO。另外,當一個執行緒完成工作後,發現完成埠佇列中仍有事件,可以保持其活動狀態,繼續處理。
3.任何一個執行緒在阻塞後,都可以通知完成埠,使其能啟用其他等待執行緒。
完成埠之所以能夠做到保持活動執行緒數在N*CPU之內(儘量),實際上是因為每個引起執行緒阻塞的API,比如ReadFile內部,都對完成埠做了處理,ReadFile中一旦發現當前執行緒在完成埠中,它將通知該完成埠此執行緒將變為非活動的,使其按需啟用其他等待執行緒。
備註:
1.類Linux上的網路模型轉到Windows上時可能產生明顯的效能問題。一個是因為執行緒切換Windows不如Linux快,另一個是在類Linux系統上,socket的分配是從小到大,有小的空餘則立即分配。想象這個模型,用一個數組儲存socket,當連結不多時,遍歷一遍socket可能很快,有效的socket都集中的陣列起始部分。但Windows上的socket分配是隨機的,即便只有幾個連結,也可能很分散,再用陣列儲存socket,想訪問一遍為數不多的socket,基本上也得遍歷整個陣列。
參考資料:<<深入解析Windows 第四版>>,網路資料。