大家好,我是程序員田螺。今天我們一起來學習IO模型。在本文開始前呢,先問問大家幾個問題哈~
什么是IO呢?什么是阻塞非阻塞IO?什么是同步異步IO?什么是IO多路復用?select/epoll跟IO模型有什么關系?有幾種經典IO模型呢?BIO、NIO、AIO到底有什么區別的?
如果這些問題,你都能很好答上的話,那恭喜你,你對IO的掌握已經很棒啦!那你跟田螺哥一起看完這篇文章,再復習一下,加深印象吧~如果你對這些問題模棱兩可的話,那也沒關系,看完這篇文章,就理解啦!
圖片
IO,英文全稱是Input/Output,翻譯過來就是輸入/輸出。平時我們聽得挺多,就是什么磁盤IO,網絡IO。那IO到底是什么呢?是不是有種懵懵懂懂的感覺呀,好像大概知道它是什么,又好像說不清楚。
IO,即輸入/輸出,到底誰是輸入?誰是輸出呢?IO如果脫離了主體,就會讓人疑惑。
我們常說的輸入輸出,比較直觀的意思就是計算機的輸入輸出,計算機就是主體。大家是否還記得,大學學計算機組成原理的時候,有個馮.諾依曼結構,它將計算機分成分為5個部分:運算器、控制器、存儲器、輸入設備、輸出設備。
圖片
輸入設備是向計算機輸入數據和信息的設備,鍵盤,鼠標都屬于輸入設備;輸出設備是計算機硬件系統的終端設備,用于接收計算機數據的輸出顯示,一般顯示器、打印機屬于輸出設備。
例如你在鼠標鍵盤敲幾下,它就會把你的指令數據,傳給主機,主機通過運算后,把返回的數據信息,輸出到顯示器。
鼠標、顯示器這只是直觀表面的輸入輸出,回到計算機架構來說,涉及計算機核心與其他設備間數據遷移的過程,就是IO。如磁盤IO,就是從磁盤讀取數據到內存,這算一次輸入,對應的,將內存中的數據寫入磁盤,就算輸出。這就是IO的本質。
我們要將內存中的數據寫入到磁盤的話,主體會是什么呢?主體可能是一個應用程序,比如一個Java進程(假設網絡傳來二進制流,一個Java進程可以把它寫入到磁盤)。
操作系統負責計算機的資源管理和進程的調度。我們電腦上跑著的應用程序,其實是需要經過操作系統,才能做一些特殊操作,如磁盤文件讀寫、內存的讀寫等等。因為這些都是比較危險的操作,不可以由應用程序亂來,只能交給底層操作系統來。也就是說,你的應用程序要把數據寫入磁盤,只能通過調用操作系統開放出來的API來操作。
- 什么是用戶空間?什么是內核空間?
- 以32位操作系統為例,它為每一個進程都分配了4G(2的32次方)的內存空間。這4G可訪問的內存空間分為二部分,一部分是用戶空間,一部分是內核空間。內核空間是操作系統內核訪問的區域,是受保護的內存空間,而用戶空間是用戶應用程序訪問的內存區域。
我們應用程序是跑在用戶空間的,它不存在實質的IO過程,真正的IO是在操作系統執行的。即應用程序的IO操作分為兩種動作:IO調用和IO執行。IO調用是由進程(應用程序的運行態)發起,而IO執行是操作系統內核的工作。此時所說的IO是應用程序對操作系統IO功能的一次觸發,即IO調用。
應用程序發起的一次IO操作包含兩個階段:
操作系統內核完成IO操作還包括兩個過程:
圖片
其實IO就是把進程的內部數據轉移到外部設備,或者把外部設備的數據遷移到進程內部。外部設備一般指硬盤、socket通訊的網卡。一個完整的IO過程包括以下幾個步驟:
我們已經知道IO是什么啦,那什么是阻塞IO呢?
假設應用程序的進程發起IO調用,但是如果內核的數據還沒準備好的話,那應用程序進程就一直在阻塞等待,一直等到內核數據準備好了,從內核拷貝到用戶空間,才返回成功提示,此次IO操作,稱之為阻塞IO。
圖片
如果內核數據還沒準備好,可以先返回錯誤信息給用戶進程,讓它不需要等待,而是通過輪詢的方式再來請求。這就是非阻塞IO,流程圖如下:
圖片
非阻塞IO的流程如下:
非阻塞IO模型,簡稱NIO,Non-Blocking IO。它相對于阻塞IO,雖然大幅提升了性能,但是它依然存在性能問題,即頻繁的輪詢,導致頻繁的系統調用,同樣會消耗大量的CPU資源。可以考慮IO復用模型,去解決這個問題。
既然NIO無效的輪詢會導致CPU資源消耗,我們等到內核數據準備好了,主動通知應用進程再去進行系統調用,那不就好了嘛?
在這之前,我們先來復習下,什么是文件描述符fd(File Descriptor),它是計算機科學中的一個術語,形式上是一個非負整數。當程序打開一個現有文件或者創建一個新文件時,內核向進程返回一個文件描述符。
IO復用模型核心思路:系統給我們提供一類函數(如我們耳濡目染的select、poll、epoll函數),它們可以同時監控多個fd的操作,任何一個返回內核數據就緒,應用進程再發起recvfrom系統調用。
應用進程通過調用select函數,可以同時監控多個fd,在select函數監控的fd中,只要有任何一個數據狀態準備就緒了,select函數就會返回可讀狀態,這時應用進程再發起recvfrom請求去讀取數據。
圖片
非阻塞IO模型(NIO)中,需要N(N>=1)次輪詢系統調用,然而借助select的IO多路復用模型,只需要發起一次詢問就夠了,大大優化了性能。
但是呢,select有幾個缺點:
因為存在連接數限制,所以后來又提出了poll。與select相比,poll解決了連接數限制問題。但是呢,select和poll一樣,還是需要通過遍歷文件描述符來獲取已經就緒的socket。如果同時連接的大量客戶端,在一時刻可能只有極少處于就緒狀態,伴隨著監視的描述符數量的增長,效率也會線性下降。
因此經典的多路復用模型epoll誕生。
為了解決select/poll存在的問題,多路復用模型epoll誕生,它采用事件驅動來實現,流程圖如下:
圖片
epoll先通過epoll_ctl()來注冊一個fd(文件描述符),一旦基于某個fd就緒時,內核會采用回調機制,迅速激活這個fd,當進程調用epoll_wait()時便得到通知。這里去掉了遍歷文件描述符的坑爹操作,而是采用監聽事件回調的機制。這就是epoll的亮點。
我們一起來總結一下select、poll、epoll的區別
select | poll | epoll | |
底層數據結構 | 數組 | 鏈表 | 紅黑樹和雙鏈表 |
獲取就緒的fd | 遍歷 | 遍歷 | 事件回調 |
事件復雜度 | O(n) | O(n) | O(1) |
最大連接數 | 1024 | 無限制 | 無限制 |
fd數據拷貝 | 每次調用select,需要將fd數據從用戶空間拷貝到內核空間 | 每次調用poll,需要將fd數據從用戶空間拷貝到內核空間 | 使用內存映射(mmap),不需要從用戶空間頻繁拷貝fd數據到內核空間 |
epoll明顯優化了IO的執行效率,但在進程調用epoll_wait()時,仍然可能被阻塞。能不能醬紫:不用我老是去問你數據是否準備就緒,等我發出請求后,你數據準備好了通知我就行了,這就誕生了信號驅動IO模型。
信號驅動IO不再用主動詢問的方式去確認數據是否就緒,而是向內核發送一個信號(調用sigaction的時候建立一個SIGIO的信號),然后應用用戶進程可以去做別的事,不用阻塞。當內核數據準備好后,再通過SIGIO信號通知應用進程,數據準備好后的可讀狀態。應用用戶進程收到信號之后,立即調用recvfrom,去讀取數據。
圖片
信號驅動IO模型,在應用進程發出信號后,是立即返回的,不會阻塞進程。它已經有異步操作的感覺了。但是你細看上面的流程圖,發現數據復制到應用緩沖的時候,應用進程還是阻塞的。回過頭來看下,不管是BIO,還是NIO,還是信號驅動,在數據從內核復制到應用緩沖的時候,都是阻塞的。還有沒有優化方案呢?AIO(真正的異步IO)!
前面講的BIO,NIO和信號驅動,在數據從內核復制到應用緩沖的時候,都是阻塞的,因此都不算是真正的異步。AIO實現了IO全流程的非阻塞,就是應用進程發出系統調用后,是立即返回的,但是立即返回的不是處理結果,而是表示提交成功類似的意思。等內核數據準備好,將數據拷貝到用戶進程緩沖區,發送信號通知用戶進程IO操作執行完畢。
流程如下:
圖片
異步IO的優化思路很簡單,只需要向內核發送一次請求,就可以完成數據狀態詢問和數據拷貝的所有操作,并且不用阻塞等待結果。日常開發中,有類似思想的業務場景:
比如發起一筆批量轉賬,但是批量轉賬處理比較耗時,這時候后端可以先告知前端轉賬提交成功,等到結果處理完,再通知前端結果即可。
圖片
IO模型 | |
阻塞I/O模型 | 同步阻塞 |
非阻塞I/O模型 | 同步非阻塞 |
I/O多路復用模型 | 同步阻塞 |
信號驅動I/O模型 | 同步非阻塞 |
異步IO(AIO)模型 | 異步非阻塞 |
一個經典生活的例子:
本文鏈接:http://www.www897cc.com/showinfo-26-79297-0.html看一遍就理解:IO模型詳解
聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com