IO模型(Java)
io模型本是UNIX網路程式設計相關的內容。UNIX中IO模型有五種,包括阻塞 I/O、非阻塞 I/O、I/O 複用、訊號驅動式 I/O 、非同步 I/O 等。
阻塞 I/O 模型
阻塞式IO模型,一般表現為程序或執行緒等待某個條件,如果條件不滿足,則一直等下去。條件滿足,則進行下一步操作。如下圖:
非阻塞 I/O 模型
非阻塞式IO模型:程序或執行緒目的未達到時,不再一味的等著,而是直接返回。然後通過輪詢的方式,不停的去問核心資料準備好沒。如下圖:
I/O 複用模型
Unix/Linux 環境下的 I/O 複用模型包含三組系統呼叫,分別是 select、poll 和 epoll(FreeBSD 中則為 kqueue)。select 出現的時間最早,在 BSD 4.2中被引入。poll 則是在 AT&T System V UNIX 版本中被引入(詳情請參考 UNIX man-page)。epoll 出現在 Linux kernel 2.5.44 版本中,與之對應的 kqueue 呼叫則出現在 FreeBSD 4.1,早於 epoll。select 和 poll 出現的時間比較早,在當時也是比較先進的 I/O 模型了,滿足了當時的需求。不過隨著因特網使用者的增長,C10K 問題出現。select 和 poll 已經不能滿足需求了,研發更加高效的 I/O 模型迫在眉睫。到了 2000 年,FreeBSD 率先發布了 select、poll 的改進版 kqueue。Linux 平臺則在 2002 年 2.5.44 中釋出了 epoll。
select模式:select 有三個檔案描述符集(readfds),分別是可讀檔案描述符集(writefds)、可寫檔案描述符集和異常檔案描述符集(exceptfds)。應用程式可將某個 socket (檔案描述符)設定到感興趣的檔案描述符集中,並呼叫 select 等待所感興趣的事件發生。比如某個 socket 處於可讀狀態了,此時應用程序就可呼叫 recvfrom 函式把資料從核心空間拷貝到程序空間內,無需再等待核心準備資料了。如下圖:
一般情況下,應用程序會將多個 socket 設定到感興趣的檔案描述符集中,並呼叫 select 等待所關注的事件(比如可讀、可寫)處於就緒狀態。當某些 socket 處於就緒狀態後,select 返回處於就緒狀態的 sockct 數量。注意這裡返回的是 socket 的數量,並不是具體的 socket。應用程式需要自己去確定哪些 socket 處於就緒狀態了,確定之後即可進行後續操作。
訊號驅動式 I/O 模型
訊號驅動式 I/O 模型是指,應用程序告訴核心,如果某個 socket 的某個事件發生時,請向我發一個訊號。在收到訊號後,訊號對應的處理函式會進行後續處理。如下圖:
非同步 I/O 模型
非同步 I/O 是指應用程序把檔案描述符傳給核心後,啥都不管了,完全由核心去操作這個檔案描述符。核心完成相關操作後,會發訊號告訴應用程序,某某 I/O 操作我完成了,你現在可以進行後續操作了。如下圖:
上圖通過 aio_read 把檔案描述符、資料快取空間,以及訊號告訴核心,當檔案描述符處於可讀狀態時,核心會親自將資料從核心空間拷貝到應用程序指定的快取空間呢。拷貝完在告訴程序 I/O 操作結束,就可以直接使用資料了。
幾種IO模型的對比圖:
對於java的IO(BIO NIO AIO)而言,以socket.read()為例子:
傳統的BIO裡面socket.read(),如果TCP RecvBuffer裡沒有資料,函式會一直阻塞,直到收到資料,返回讀到的資料。
對於NIO,如果TCP RecvBuffer有資料,就把資料從網絡卡讀到記憶體,並且返回給使用者;反之則直接返回0,永遠不會阻塞。
最新的AIO(Async I/O)裡面會更進一步:不但等待就緒是非阻塞的,就連資料從網絡卡到記憶體的過程也是非同步的。
換句話說,BIO裡使用者最關心“我要讀”,NIO裡使用者最關心"我可以讀了",在AIO模型裡使用者更需要關注的是“讀完了”。
NIO一個重要的特點是:socket主要的讀、寫、註冊和接收函式,在等待就緒階段都是非阻塞的,真正的I/O操作是同步阻塞的(消耗CPU但效能非常高)。