IO多路復用機制詳解

服務端編程需要構建高性能的IO模型,常見的IO模型主要有以下四種

  • 同步阻塞IO
  • 同步非阻塞IO 默認創建的socket都是阻塞的,非阻塞IO要求socket設置為NONBLOCK
  • IO多路復用 經典Reactor設計模式,異步阻塞IO,select epoll
  • 異步IO 異步非阻塞IO

同步與異步 用戶線程與內核的交互方式;同步是指用戶發起IO請求後,需要等待或者輪詢內核IO操作完成後才能繼續執行;異步是指用戶線程發起IO請求後繼續執行,當內核操作完成後會通知線程或者調用用戶線程註冊的回調函數

阻塞與非阻塞 用戶線程調用內核IO操作的方式;阻塞是指IO操作需要徹底完成後才返回到用戶空間,而非阻塞是指IO操作被調用後立即返回給用戶一個狀態值

同步阻塞IO

用戶線程通過系統調用read發起IO操作,由用戶空間轉到內核空間,內核等到數據包到達以後,將接受的數據拷貝到用戶空間,完成read,用戶需要等待read將socket中的數據讀取到buffer後,才繼續處理接收的數據,整個IO請求過程中,用戶線程是被阻塞的,導致用戶發起請求時,不能做任何事情,對CPU資源利用不夠。

 

同步非阻塞IO

同步非阻塞io,在同步阻塞io的基礎上,將socket設置為nonblock,用戶線程可以在發起io請求後立即返回;socket是非阻塞的方式,用戶線程發起IO請求時立即返回,但並未讀取到任何數據,用戶線程需要不斷發起IO請求,直到數據到達後,才真正讀取到數據,繼續執行;在整個IO請求的過程中,雖然用戶線程每次發起IO請求後可以立即返回,但是為了等到數據,仍需要不斷地輪詢、重複請求、消耗大量CPU資源,一般很少使用這種模型,而是在其他IO模型中使用非阻塞IO

 

 

IO多路復用

IO多路復用,是建立在內核上提供的多路分離函數select基礎之上的,使用select函數可以避免同步非阻塞IO模型中輪詢等待的問題;用戶將需要進行IO操作的socket添加到select中,然後阻塞等待select系統調用返回。當數據到達時,socket被激活,select函數返回,用戶線程發起read請求,讀取數據並繼續執行。使用select函數進行IO請求與同步阻塞模型並無太大區別,甚至多添加監視socket,select函數額外操作,使用優勢主要在於用戶可以在一個線程內同時處理多個socket的IO請求,用戶可以註冊多個socket,然後不斷調用select讀取被激活的socket,即可達到在同一個線程內同時處理多個IO請求的目的,同步阻塞模型中,必須使用多線程。

 

使用select允許單線程內處理多個IO請求,但是每個IO請求的過程還是阻塞的,平均時間甚至比同步阻塞IO模型還要長,IO多路復用模型使用Reactor設計模式實現了這一機制,用戶線程只註冊自己感興趣的socket或者IO請求,去做自己的事情,等到數據到來時再進行處理,可以提高cpu利用率;EventHandler抽象類表示IO事件處理器,擁有IO句柄get-handle,以及對Handle的操作handle-event,繼承於EventHandler的子類可以對事件處理器的行為進行定製,Reactor類用於管理EventHandler註冊、刪除,並使用handle-events實現事件循環,不斷調用內核中的多路分離函數select,只要某個文件句柄被激活,select就返回,就會調用handle-event事件處理器進行操作。

 

通過reactor的方式,將用戶線程輪詢IO操作狀態的工作交給handle-even進行處理,用戶線程進行事件註冊之後進行其他工作(異步),而reactor線程負責調用內核select函數,當存在socket被激活時,通知相應的用戶線程,執行handle-event進行數據讀取、處理工作,由於select函數是阻塞的,所以多路IO復用也被稱為異步阻塞IO模型,socket是不被阻塞的,用戶發起IO請求時,數據已經到達,用戶線程一定不會被阻塞。其使用會阻塞線程的select系統調用,因此IO多路復用只能稱為異步阻塞IO,而非真正的異步IO。

 

異步IO

 真正的異步IO,需要操作系統更強的支持,在IO多路復用中,事件循環將文件句柄的狀態事件通知給用戶線程,由用戶線程自行讀取數據、處理數據,而在異步IO模型中,當用戶線程收到通知時,數據已經被內核讀取完畢,並放在用戶線程指定的緩衝區內,內核在IO完成後通知用戶線程直接使用即可。異步模型使用Proactor設計模式實現這一機制。

Proactor和Reactor模式在結構上比較相似,在Client使用方式上差別較大,Proactor模式中,用戶線程將AO、Proactor以及操作完成時的CompletionHandler註冊到AOP。AOP使用Facade模式提供一組異步操作API供用戶使用,當用戶線程調用異步API後,便執行自己的任務。AOP會開啟獨立的內核線程執行異步操作,當異步IO完成時,AOP將用戶線程與AOP一起註冊的Proactor和CompletionHandler取出,然後將CompletionHandler與IO操作的結果一致轉發給Proactor,Proactor負責回調每一個異步操作事件完成處理函數handle-event,Proactor模式中每個異步操作都可以綁定一個proactor對象,一般操作系統中Proactor為單例模式,以便集中化分發操作完成事件。

 

異步IO模型中,用戶線程直接使用內核提供的異步IO API發起read請求,發起後立即返回,繼續執行用戶線程代碼。此時用戶線程已經將調用的AO與CH註冊到了內核,然後操作系統開啟獨立的內核線程去處理IO操作。當read請求的數據到達時,由內核負責讀取socket中的數據,並寫入用戶指定的緩衝區中。最後內核將read的數據和用戶線程註冊的CH分發給內部Proactor,Proactor將IO完成的信息通知給用戶線程,完成異步IO。

異步IO並不常見,高性能並發服務程序,使用IO多路復用模型+多線程任務處理的架構基本可以滿足要求,目前操作系統對異步IO的支持並非特別完善,更多的是採用IO多路復用模型模擬異步IO的方式,IO事件觸發時不直接通知用戶線程,而是將數據讀寫完畢後放到用戶指定的緩衝區中。