BIO,NIO,AIO概覽

pjmike_pj發表於2018-08-24

前言

在Java中,有三種IO模型: BIO,NIO,AIO。介紹這三種IO模型之前,需要介紹一下同步,非同步與阻塞,非阻塞的概念,然後再從Java和Linux OS的角度去分析BIO,NIO和AIO

同步與非同步

同步

同步就是發起一個呼叫後,被呼叫者未處理完請求之前,呼叫不返回。

通俗的例子描述同步就像:

你打電話問書店老闆有沒有《葵花寶典》這本書的時候,如果是同步機制,書店老闆會說,你稍等,”我查一下",然後開始查啊查,等查好了(可能是5秒,也可能是一天)告訴你結果(返回結果)。

非同步

非同步就是發起一個呼叫後,立刻得到被呼叫者的迴應表示已接收到請求,但是被呼叫者並沒有返回結果,此時我們可以處理其他的請求,被呼叫者通常依靠事件,回撥等機制來通知呼叫者其返回結果。

通俗的例子描述非同步就像:

而非同步機制,書店老闆直接告訴你我查一下啊,查好了打電話給你,然後直接掛電話了(不返回結果)。然後查好了,他會主動打電話給你。在這裡老闆通過“回電”這種方式來回撥

此處參照知乎上關於此問題的回答:www.zhihu.com/question/19…

再次總結一下同步與非同步:

同步與非同步最大的區別就是被呼叫方執行方式返回時機,同步指的是被呼叫方做完事情之後再返回,非同步指的是被呼叫方先返回,然後再做事情,做完之後再想辦法通知呼叫方

阻塞與非阻塞

阻塞

阻塞就是發起一個請求,呼叫者一直等待請求結果返回,也就是當前執行緒會被掛起,無法從事其他任務,只有當條件就緒才能繼續。

非阻塞

非阻塞就是發起一個請求,呼叫者不用一直等著結果返回,可以先去幹其他事情。

還是上面買書的例子:

你打電話問書店老闆有沒有《葵花寶典》這本書,你如果是阻塞式呼叫,你會一直把自己“掛起”,直到得到這本書有沒有的結果,如果是非阻塞式呼叫,你不管老闆有沒有告訴你,你自己先一邊去玩了, 當然你也要偶爾過幾分鐘check一下老闆有沒有返回結果。

同步、非同步和阻塞、非阻塞的區別

阻塞和同步不是一回事,同步,非同步與阻塞,非阻塞針對的物件是不一樣的,阻塞,非阻塞是說的呼叫者,同步,非同步說的是被呼叫者

BIO、NIO、AIO概覽

  • BIO(Blocking I/O):BIO也就是傳統的同步阻塞IO模型,對應Java.io包,它提供了很多IO功能,比如輸入輸出流,對檔案進行操作。在網路程式設計(Socket通訊)中也同樣進行IO操作。

  • NIO(New I/O): NIO是一種同步非阻塞的I/O模型,在Java 1.4 中引入了NIO框架,對應 java.nio 包,提供了 Channel , Selector,Buffer等抽象

  • AIO: AIO 也就是 NIO 2。在 Java 7 中引入了 NIO 的改進版 NIO 2,它是非同步非阻塞的IO模型

Linux的5種 I/O模型

上面簡單介紹了Java中的三種IO模型,三種模型提供的與IO有關的API,在檔案處理時,底層實際上是依賴作業系統層面的IO操作實現的,比如在Linux 2.6以後,Java中的NIO和AIO都是通過 epoll來實現的,關於epoll等概念後面也會闡述。

而實際上在Linux(Unix)作業系統中,共有五種 IO模型,分別是:阻塞IO模型非阻塞IO模型IO複用模型訊號驅動IO模型以及非同步IO模型,而4種都是同步的,只有最後一種是非同步的。下面的分析主要參考了《UNIX網路程式設計 卷1:套接字聯網API(第3版)中的介紹。

阻塞IO模型 - BIO

阻塞IO

一個輸入操作通常包括兩個不同的階段:

  • 等待資料準備好
  • 從核心向程式複製資料

對於一個套接字上的輸入操作,第一步通常涉及等待資料從網路中到達,當所等待分組到達時,它被複制到核心中的某個緩衝區,第二步就是把資料從核心緩衝區複製到應用程式緩衝區。

從上圖可以看出,應用程式通過 系統呼叫 recvfrom 去接收資料,而由於核心資料沒有準備好,應用程式就會阻塞,直到核心準備好資料並將其從核心複製到應用程式的緩衝區中或者發生錯誤才返回。最常見的錯誤就是系統呼叫被訊號中斷。程式從呼叫recvfrom開始到它返回的整段時間內是被阻塞的。

Linux下的阻塞式I/O模型就對應了Java下的BIO模型,BIO的底層實現是呼叫作業系統的API去執行的,也就是呼叫作業系統的Socket套接字。

非阻塞式I/O模型 - NIO

非阻塞IO

應用程式通過系統呼叫 recvfrom 不斷的去和核心互動,直到核心資料包準備好,而如果核心無資料準備好,轉而立即返回一個 EWOULDBLOCK的錯誤,過一段時間再次傳送 recvfrom請求,在此期間程式可以做其他事情,不用一直等待,這就是非阻塞。

當一個應用程式迴圈呼叫 recvfrom時,我們稱之為輪詢(polling),應用程式持續輪詢核心,以檢視某個操作是否就緒。Java的NIO對映到Linux作業系統就是如上圖所示的非阻塞I/O模型

I/O複用模型

IO複用模型

IO多路複用使用select/poll/epoll函式,多個程式的IO都可以註冊在同一個 select 上,當使用者程式呼叫該 select時,select去監聽所有註冊好的IO,如果所有被監聽的IO需要的資料都沒有準備好,那麼 select呼叫程式會被阻塞,只要任意一個IO的資料包套接字變為可讀,即資料包已經準備好,select 就返回套接字可讀這一條件,然後呼叫 recvfrom把所讀資料包復制到應用程式緩衝區。

強調一點就是,IO多路複用模型並沒有涉及到非阻塞,程式在發出select後,要一直阻塞等待其監聽的所有IO操作至少有一個資料準備好才返回,強調阻塞狀態,不存在非阻塞。

而在 Java NIO中也可以實現多路複用,主要是利用多路複用器 Selector,與這裡的 select函式型別,Selector會不斷輪詢註冊在其上的通道Channel,如果有某一個Channel上面發生讀或寫事件,這個Channel處於就緒狀態,就會被Selector輪詢出來。關於Java NIO實現多路複用更多的介紹請查詢相關文章。

I/O多路複用的應用場景

I/O 多路複用的主要應用場景如下:

  • 伺服器需要同時處理多個處於監聽狀態或者多個連線狀態的套接字
  • 伺服器需要同時處理多種網路協議的套接字

I/O多路複用的系統呼叫函式

目前支援I/O 多路複用的系統呼叫函式有 select,pselect,poll,epoll。在Linux 網路程式設計中,很長一段時間都使用select 做輪詢和網路事件通知。然而因為select的一些固有缺陷導致它的應用受到了很大的限制,比如select 單個程式開啟的最大控制程式碼數是有限的。最終在 Linux 2.6 選擇epoll 替代了select,Java NIO和AIO底層就是用epoll。更多關於這些系統呼叫的介紹與使用,請參閱 《UNIX網路程式設計 卷1:套接字聯網API(第3版)

訊號驅動式I/O模型

訊號

應用程式預先向核心安裝一個訊號處理函式,然後立即返回,程式繼續工作,不阻塞,當資料包準備好讀取時,核心就為該程式產生一個訊號通知程式,然後程式再呼叫recvfrom讀取資料包。

訊號驅動式IO不是非同步的

訊號驅動式IO在資料準備階段是非同步的,當核心中有資料包準備後再通知程式,但是在呼叫 recvfrom操作進行資料拷貝時是同步的,所以總體來說,整個IO過程不能是非同步的。

非同步I/O模型 - AIO

非同步IO

應用程式呼叫aio_read函式,給核心傳遞描述符,快取區指標,快取區大小和檔案偏移,並告訴核心當整個操作完成時如何通知程式,然後該系統呼叫立即返回,而且在等待I/O完成期間,我們的程式不被阻塞,程式可以去幹其他事情,然後核心開始等待資料準備,資料準備好以後再拷貝資料到程式緩衝區,最後通知整個IO操作已完成。

Java的AIO提供了非同步通道API,其作業系統底層實現就是這個非同步I/O模型

與訊號驅動式I/O的區別

主要區別在於: 訊號驅動式I/O是由核心通知我們何時去啟動一個I/O操作,而非同步I/O模型是由核心通知我們I/O操作何時完成。

5種I/O模型的比較

5

由上圖可以再次看出,IO操作主要分為兩個階段:

  • 等待資料包準備階段
  • 資料拷貝階段

前4種IO模型都是同步IO模型,為什麼說都是同步的,因為它們在第二步資料拷貝階段都是阻塞的,這會導致整個請求程式存在阻塞的情況,所以是同步的,而非同步IO模型不會導致請求程式阻塞。

小結

上面簡要闡述了BIO,NIO,AIO以及Linux下的5種IO模型,對於IO模型更加詳細的介紹參考《UNIX網路程式設計 卷1:套接字聯網API(第3版)

參考資料 & 鳴謝

相關文章