IO 模型這塊確實挺難理解的,需要太多計算機底層知識。寫這篇文章用了挺久,就非常希望能把我所知道的講出來吧!希望朋友們能有收貨!為了寫這篇文章,還翻看了一下《UNIX 網絡編程》這本書,太難了,我滴乖乖!心痛~
個人能力有限。如果文章有任何需要補充/完善/修改的地方,歡迎在評論區指出,共同進步!
前言
I/O 一直是很多小伙伴難以理解的一個知識點,這篇文章我會將我所理解的 I/O 講給你聽,希望可以對你有所幫助。
I/O
何為 I/O?
I/O(Input/Outpu) 即輸入/輸出 。
我們先從計算機結構的角度來解讀一下 I/O。
根據馮.諾依曼結構,計算機結構分為 5 大部分:運算器、控制器、存儲器、輸入設備、輸出設備。
輸入設備(比如鍵盤)和輸出設備(比如鼠標)都屬於外部設備。網卡、硬盤這種既可以屬於輸入設備,也可以屬於輸出設備。
輸入設備向計算機輸入數據,輸出設備接收計算機輸出的數據。
從計算機結構的視角來看的話, I/O 描述了計算機系統與外部設備之間通信的過程。
我們再先從應用程序的角度來解讀一下 I/O。
根據大學里學到的操作系統相關的知識:為了保證操作系統的穩定性和安全性,一個進程的地址空間划分為 用戶空間(User space) 和 內核空間(Kernel space ) 。
像我們平常運行的應用程序都是運行在用戶空間,只有內核空間才能進行系統態級別的資源有關的操作,比如如文件管理、進程通信、內存管理等等。也就是說,我們想要進行 IO 操作,一定是要依賴內核空間的能力。
並且,用戶空間的程序不能直接訪問內核空間。
當想要執行 IO 操作時,由於沒有執行這些操作的權限,只能發起系統調用請求操作系統幫忙完成。
因此,用戶進程想要執行 IO 操作的話,必須通過 系統調用 來間接訪問內核空間
我們在平常開發過程中接觸最多的就是 磁盤 IO(讀寫文件) 和 網絡 IO(網絡請求和相應)。
從應用程序的視角來看的話,我們的應用程序對操作系統的內核發起 IO 調用(系統調用),操作系統負責的內核執行具體的 IO 操作。也就是說,我們的應用程序實際上只是發起了 IO 操作的調用而已,具體 IO 的執行是由操作系統的內核來完成的。
當應用程序發起 I/O 調用后,會經歷兩個步驟:
- 內核等待 I/O 設備准備好數據
- 內核將數據從內核空間拷貝到用戶空間。
有哪些常見的 IO 模型?
UNIX 系統下, IO 模型一共有 5 種: 同步阻塞 I/O、同步非阻塞 I/O、I/O 多路復用、信號驅動 I/O 和異步 I/O。
這也是我們經常提到的 5 種 IO 模型。
Java 中 3 種常見 IO 模型
BIO (Blocking I/O)
BIO 屬於同步阻塞 IO 模型 。
同步阻塞 IO 模型中,應用程序發起 read 調用后,會一直阻塞,直到在內核把數據拷貝到用戶空間。
在客戶端連接數量不高的情況下,是沒問題的。但是,當面對十萬甚至百萬級連接的時候,傳統的 BIO 模型是無能為力的。因此,我們需要一種更高效的 I/O 處理模型來應對更高的並發量。
NIO (Non-blocking/New I/O)
Java 中的 NIO 於 Java 1.4 中引入,對應 java.nio
包,提供了 Channel
, Selector
,Buffer
等抽象。NIO 中的 N 可以理解為 Non-blocking,不單純是 New。它支持面向緩沖的,基於通道的 I/O 操作方法。 對於高負載、高並發的(網絡)應用,應使用 NIO 。
Java 中的 NIO 可以看作是 I/O 多路復用模型。也有很多人認為,Java 中的 NIO 屬於同步非阻塞 IO 模型。
跟着我的思路往下看看,相信你會得到答案!
我們先來看看 同步非阻塞 IO 模型。
同步非阻塞 IO 模型中,應用程序會一直發起 read 調用,等待數據從內核空間拷貝到用戶空間的這段時間里,線程依然是阻塞的,直到在內核把數據拷貝到用戶空間。
相比於同步阻塞 IO 模型,同步非阻塞 IO 模型確實有了很大改進。通過輪詢操作,避免了一直阻塞。
但是,這種 IO 模型同樣存在問題:應用程序不斷進行 I/O 系統調用輪詢數據是否已經准備好的過程是十分消耗 CPU 資源的。
這個時候,I/O 多路復用模型 就上場了。
IO 多路復用模型中,線程首先發起 select 調用,詢問內核數據是否准備就緒,等內核把數據准備好了,用戶線程再發起 read 調用。read 調用的過程(數據從內核空間->用戶空間)還是阻塞的。
目前支持 IO 多路復用的系統調用,有 select,epoll 等等。select 系統調用,是目前幾乎在所有的操作系統上都有支持
- select 調用 :內核提供的系統調用,它支持一次查詢多個系統調用的可用狀態。幾乎所有的操作系統都支持。
- epoll 調用 :linux 2.6 內核,屬於 select 調用的增強版本,優化了 IO 的執行效率。
IO 多路復用模型,通過減少無效的系統調用,減少了對 CPU 資源的消耗。
Java 中的 NIO ,有一個非常重要的選擇器 ( Selector ) 的概念,也可以被稱為 多路復用器。通過它,只需要一個線程便可以管理多個客戶端連接。當客戶端數據到了之后,才會為其服務。
AIO (Asynchronous I/O)
AIO 也就是 NIO 2。Java 7 中引入了 NIO 的改進版 NIO 2,它是異步 IO 模型。
異步 IO 是基於事件和回調機制實現的,也就是應用操作之后會直接返回,不會堵塞在那里,當后台處理完成,操作系統會通知相應的線程進行后續的操作。
目前來說 AIO 的應用還不是很廣泛。Netty 之前也嘗試使用過 AIO,不過又放棄了。這是因為,Netty 使用了 AIO 之后,在 Linux 系統上的性能並沒有多少提升。
最后,來一張圖,簡單總結一下 Java 中的 BIO、NIO、AIO。
參考
- 《深入拆解 Tomcat & Jetty》
- 如何完成一次 IO:https://llc687.top/post/如何完成一次-io/
- 程序員應該這樣理解 IO:https://www.jianshu.com/p/fa7bdc4f3de7
- 10 分鍾看懂, Java NIO 底層原理:https://www.cnblogs.com/crazymakercircle/p/10225159.html
- IO 模型知多少 | 理論篇:https://www.cnblogs.com/sheng-jie/p/how-much-you-know-about-io-models.html
- 《UNIX 網絡編程 卷 1;套接字聯網 API 》6.2 節 IO 模型