Study Notes - I/O Models


很久以前在研究 nginx 時,過程針對他的 I/O Model 有了初步的了解,但是追本朔源還是經典著作 UNIX Network Programming Chapter 6. I/O Multiplexing,本文整理 BIO、NIO、AIO 等著名的 I/O Models 筆記。

W. Richard Stevens 美國電腦科學家,有多本經典著作,像是 UNIX Network ProgrammingTCP/IP Illustrated 系列


名詞

只要是處理網路、Disk 有關的 I/O 應用,或者處理 Process / Thread 的問題,則會出現同步機制問題,很常會出現以下的名詞:

阻塞 (Blocking) 與非阻塞 (Non-Blocking)

阻塞 (Blocking) 與非阻塞 (Non-Blocking) 描述的是 請求 在等待結果時的 狀態

  • 阻塞 (Blocking):調用的程序或者應用程式發起請求,在獲得結果之前,調用方的程序會懸 (Hang) 住不動,無法回應,直到獲得結果。
  • 非阻塞 (Non-Blocking):概念與阻塞相同,但是調用方不會因為等待結果,而懸著不動。後續通常透過輪詢機制 (Polling) 機制取得結果。

同步 (Synchronous) 與 非同步 (Asynchronous)

同步 (Synchronous) 與 非同步 (Asynchronous) 描述的是:使用者執行緒與 Kernel 的 通訊模式

  • 同步 (Synchronous):使用者執行緒發出 I/O 請求後,要等待、或者輪詢 Kernel I/O 的操作完成後,才能繼續執行。
    • 等待 Kernel 回覆:Blocking IO,縮寫成 BIO
    • 輪詢類似於 Non-Blocking IO,縮寫成 NIO
  • 非同步 (Asynchronous):或稱 異步,使用者執行緒發出 I/O 請求後仍然繼續執行下一個操作,當 Kernel I/O 操作結束後,會通知執行緒,或者呼叫 callback 函數。

同步 中文的意思很容易誤解為,很多事同時做,實際上是事情有 先後關係 的概念,也就是 有序性 (oredered);而 非同步 才是類似於很多事情在同一個時間一起發動,他是 無序性 (non-ordered)


I/O Models

利用 I/O Models 當關鍵字,可以找到很多篇文章在整理這些概念。下面的圖是我重新整理的 I/O Models 矩陣,依據 Blocking / Non-Blocking、Synchronize / Asynchronous 的組合,產生的四個象限如下圖:

搭配書本提到的五種方式:

  1. Blocking I/O model (Blocking / Synchronous): BIO,阻塞 I/O
  2. Non-Blocking I/O (Non-Blocking / Synchronous): NIO,非阻塞 I/O
  3. I/O Multiplexing (Blocking / Asynchronous): I/O 多工 (多路複用)
  4. Signal-driven I/O (SIGIO): 訊號驅動,屬於 Synchronous I/O
  5. Asynchronous I/O (Non-Blocking / Asynchronous): AIO
    • 在 Java7 裡面稱為 NIO2

一次的 Network I/O 都會涉及到兩個層次:先是呼叫此 I/O 應用程式的 Process / Thread,再來就是 Kernel。一個 read 動作會有以下兩個階段:

  1. 等待資料準備好 (Waiting for the data to be ready).
    • This involves waiting for data to arrive on the network. When the packet arrives, it is copied into a buffer within the kernel.
  2. 把資料從 Kernel 複製到 Process (Copying the data from the kernel to the process).
    • This means copying the (ready) data from the kernel’s buffer into our application buffer

這兩個階段的些微差異,產生了這五種 I/O Models。依照我自己的理解,重新繪製了他們的時序圖,然後用我自己的語言重新解釋圖中的意思,整理如下。

1. Blocking I/O Model (BIO)

阻塞發生在執行緒 (Thread) 處於執行中,無法處理其他任務造成的現象,除非滿足特定條件讓 Thread 繼續做事。最常見的例子就是寫入資料的操作,需要等到 I/O 結束,才能繼續下一個動作。

Blocking I/O 的特性:

  • 應用程式的程序不會詢問 Kernel 資料是否已經準備好了,直到 Kernel 回覆給應用程式。
  • Java 的 FileInputStream、FileOutputStream、Socket R/W 都是 BIO.

2. Non-Blocking I/O (NIO)

執行過程與 BIO 一樣,如果無法完成,則返回 EAGINEWOULDBLOCK

Non-Blocking I/O 的特性:

  • 應用程式的程序會不斷的詢問 Kernel 資料是否已經準備好了。
  • 抽象概念來講,NIO 等同於 AIO,只是 NIO 是透過輪詢 (Polling) 方式取得結果,AIO 則有更多選擇。

3. I/O Multiplexing

多工 (Multiplexing, 或翻譯成多路複用),在矩陣中屬於 Blocking、Asynchronous 的組合。

最常見的 I/O Multiplexing 實作:

5. Asynchronous I/O

  1. 應用程式的程序告訴 Kernel 做一個操作 (Operation),不等 Kernel 回覆,程序繼續執行。
  2. Kernel 完成整個操作,包含取的資料,複製到 Buffer 之後,通知程序 (deliver signal specified in aio_read)


結論

程式語言的 I/O 通常都會依賴於 OS,例如:

  • Java 不同平台的 JRE 使用不同方式實作 I/O Multiplexing
  • Node.js 則依賴於 libuv

生活中的 I/O Model

解釋阻塞、非阻塞、同步、非同步很常用到便利商店排隊的例子。

可以這樣想像,Blocking 與否,代表著客人的狀態,而 Sync 與否代表店員的處理方式。

  • Blocking I/O:一堆人去便利商店買咖啡,第一個人跟店員點完咖啡後,站在櫃檯前等待店員煮好咖啡,然後下一個接著點 …
    • Blocking: 客人只能在櫃台等待
    • Sync: 店員忙完後,才給客人咖啡
  • Non-Blocking I/O:一堆人去便利商店買咖啡,第一個人跟店員點完咖啡後,跑去逛商品,直到店員煮好咖啡通知他來拿。
    • Non-Blocking: 客人可以先去做其他事
    • Async: 店員忙完後,才給客人咖啡
  • I/O Multiplexing:店員會主動通知客人咖啡好了,但是客人還是站在櫃檯等
    • Blocking: 客人只能在櫃台等待
    • Sync: 店員固定在櫃檯,另一個在煮咖啡
  • AIO:
    1. Non-Blocking: 客人去櫃檯點餐後,店員給號碼牌就離開做其他事
    2. Async: 店員離開櫃檯去煮咖啡,另一個店員繼續在櫃台點餐
    3. 咖啡好了,叫號領咖啡。

比較 epoll, select, poll, kqueue

底下這張圖是 libevent 跑出來 的 benchmark:


Source: http://daemonforums.org/showthread.php?t=2124


延伸閱讀

參考資料


Comments