IO多路複用機制詳解

簡直?發表於2021-05-27

服務端程式設計需要構建高效能的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事件觸發時不直接通知使用者執行緒,而是將資料讀寫完畢後放到使用者指定的緩衝區中。

 

相關文章