一、同步阻塞 IO(BIO)
當使用者執行緒呼叫了 read 系統呼叫,核心(kernel)就開始了 IO 的第一個階段:準備資料。很多時候,資料在一開始還沒有到達(比如,還沒有收到一個完整的Socket資料包),這個時候 kernel 就要等待足夠的資料到來。
當 kernel 一直等到資料準備好了,它就會將資料從 kernel 核心緩衝區,拷貝到使用者緩衝區(使用者記憶體),然後 kernel 返回結果。
從使用者執行緒 read 系統呼叫開始,使用者執行緒就進入阻塞狀態,一直到 kernel 返回結果後,使用者執行緒才解除 block 的狀態,重新執行起來。
BIO 的特點:在核心進行 IO 執行的兩個階段,使用者執行緒都被 block 了。
BIO 的優點:程式簡單,在阻塞等待資料期間,使用者執行緒掛起,使用者執行緒基本不會佔用 CPU 資源。
BIO 的缺點:一般情況下,會為每個連線配套一條獨立的執行緒,或者說一條執行緒維護一個連線成功的 IO 流的讀寫。在併發量小的情況下,這個沒有什麼問題。但是,當在高併發的場景下,需要大量的執行緒來維護大量的網路連線,記憶體、執行緒切換開銷會非常巨大。因此,基本上,BIO 模型在高併發場景下是不可用的。
二、同步非阻塞 IO(NIO)
當使用者執行緒呼叫了 read 系統呼叫,立即返回,不阻塞執行緒,使用者執行緒需要不斷地發起 IO 系統呼叫輪詢資料是否準備好;
kernel 資料準備好後,使用者執行緒發起系統呼叫,使用者執行緒阻塞。核心開始複製資料,它就會將資料從 kernel 核心緩衝區,拷貝到使用者緩衝區(使用者記憶體),然後 kernel 返回結果。
使用者執行緒解除 block 狀態,重新運作起來。
NIO 的特點:應用程式的執行緒需要不斷的進行 I/O 系統呼叫,輪詢資料是否已經準備好,如果沒有準備好,繼續輪詢,直到完成系統呼叫為止。
NIO 的優點:每次發起的 IO 系統呼叫,在核心的等待資料過程中可以立即返回,使用者執行緒不會阻塞,實時性較好。
NIO 的缺點:需要不斷的重複發起 IO 系統呼叫,這種不斷的輪詢,將會不斷地詢問核心,這將佔用大量的 CPU 時間,系統資源利用率較低。
NIO 模型在高併發場景下,也是不可用的。一般 web 伺服器不直接使用這種 IO 模型,而是在其他 IO 模型中使用非阻塞 IO 這一特性。java 的實際開發中,也不會涉及這種 IO 模型。
三、IO 多路複用
當使用者執行緒呼叫了 read 系統呼叫,使用者執行緒不直接訪問 kernel ,而是進行 select/poll/epoll(多路複用器)系統呼叫。當然,這裡有一個前提,需要將目標網路連線,提前註冊到 select/poll/epoll 的可查詢 socket 列表中(這部分由 kernel 完成)。
使用者執行緒進行 select/poll/epoll 系統呼叫,執行緒阻塞,查詢可以讀的連線,kernel 會查詢所有 select/poll/epoll 的可查詢 socket 列表,當任何一個 socket 中的資料準備好了,select/poll/epoll 就會返回。
使用者執行緒獲得了目標連線後,發起 read 系統呼叫,執行緒阻塞,核心開始複製資料,它就會將資料從 kernel 核心緩衝區,拷貝到使用者緩衝區(使用者記憶體),然後 kernel 返回結果。
使用者執行緒才解除 block 的狀態,使用者執行緒終於真正讀取到資料,繼續執行。
多路複用 IO 的特點:
- 建立在作業系統 kernel 核心能夠提供的多路複用系統呼叫 select/poll/epoll 基礎之上的,多路複用 IO 需要用到兩個系統呼叫(system call), 一個 select/poll/epoll 查詢呼叫,一個是 IO 的讀取呼叫。
- 和 NIO 模型類似,多路複用 IO 需要輪詢,需要有單獨的執行緒不斷的進行 select/poll/epoll 輪詢,查詢出可以進行 IO 操作的連線。
- 多路複用 IO 模型與前面的 NIO 模型是有關係的,對於每一個可以查詢的 socket,一般都設定成為 non-blocking 模型。
多路複用 IO 的優點:用 select/poll/epoll 的優勢在於,它可以同時處理成千上萬個連線(connection)。與一條執行緒維護一個連線相比,I/O 多路複用不必建立執行緒,也不必維護這些執行緒,從而大大減小了系統的開銷。
多路複用 IO 的缺點:本質上,select/poll/epoll 系統呼叫,屬於同步 IO,也是阻塞 IO,需要在讀寫事件就緒後,自己負責進行讀寫,也就是說這個讀寫過程是阻塞的。
tips:
- "多路"指的是多個連線;"複用"指的是複用一個程式/執行緒進行監控。
- Java 的 NIO(New IO)技術,使用的就是 IO 多路複用模型。在 linux 系統上,使用的是 epoll 系統呼叫。
四、非同步非阻塞IO(AIO)
當使用者執行緒呼叫了 read 系統呼叫,使用者執行緒立刻就能去做其它的事,使用者執行緒不阻塞。
核心(kernel)就開始了 IO 的第一個階段:準備資料,當 kernel 一直等到資料準備好了,它就會將資料從 kernel 核心緩衝區,拷貝到使用者緩衝區(使用者記憶體)。
然後,kernel 會給使用者執行緒傳送一個訊號(signal),或者回撥使用者執行緒註冊的回撥介面,告訴使用者執行緒 read 操作完成了。
使用者執行緒讀取使用者緩衝區的資料,完成後續的業務操作。
AIO 的特點:
- 在核心 kernel 的等待資料和複製資料的兩個階段,使用者執行緒都不是 block 的。
- 使用者執行緒需要接受 kernel 的 IO 操作完成的事件,或者說註冊 IO 操作完成的回撥函式到作業系統的核心,因此,非同步 IO 有的時候也叫做訊號驅動 IO。
AIO 的缺點:需要完成事件的註冊與傳遞,這裡邊需要底層作業系統提供大量的支援,去做大量的工作。
目前來說, Windows 系統下通過 IOCP 實現了真正的非同步 I/O,但是,就目前的業界形式而言,Windows 系統,很少作為百萬級以上或者說高併發應用的伺服器作業系統來使用。
而在 Linux 系統下,非同步 IO 模型在2.6版本才引入,目前並不完善。所以,這也是在 Linux 下,實現高併發網路程式設計時都是以 IO 複用模型模式為主。(https://github.com/netty/netty/issues/2515)