【死磕NIO】— 阻塞IO,非阻塞IO,IO復(fù)用,信號驅(qū)動IO,異步IO,這你真的分的清楚嗎?
通過上篇文章(【死磕NIO】— 阻塞、非阻塞、同步、異步,傻傻分不清楚),我想你應(yīng)該能夠區(qū)分了什么是阻塞、非阻塞、異步、非異步了,這篇文章我們來徹底弄清楚什么是阻塞IO,非阻塞IO,IO復(fù)用,信號驅(qū)動IO,異步IO。
要想徹底弄清楚這五種IO模型,我們需要先弄清楚幾個基本概念。
基本概念
什么是IO
什么是IO?維基百科上面是這樣解釋的:
I/O(英語:Input/Output),即輸入/輸出,通常指數(shù)據(jù)在存儲器(內(nèi)部和外部)或其他周邊設(shè)備之間的輸入和輸出,是信息處理系統(tǒng)(例如計算機)與外部世界(可能是人類或另一信息處理系統(tǒng))之間的通信。輸入是系統(tǒng)接收的信號或數(shù)據(jù),輸出則是從其發(fā)送的信號或數(shù)據(jù)。
這是IO一個完整的定義,不是特別好理解,要厘清IO這個概念,我們需要從如下兩個視角來理解它。
計算機視角理解IO
馮?諾伊曼計算機的基本思想中有提到計算機硬件組成應(yīng)為五大部分:控制器,運算器,存儲器,輸入和輸出。其中輸入是指將數(shù)據(jù)輸入到計算機的設(shè)備,輸出是指從計算機中獲取數(shù)據(jù)的設(shè)備。對于計算機而言,任何涉及到計算機核心(CPU和內(nèi)存)與其他設(shè)備間的數(shù)據(jù)轉(zhuǎn)移的過程就是IO。
IO 對于計算機而言,有兩層意思:
IO 設(shè)備。比如我們最常見的打印機、鼠標(biāo)、鍵盤 對IO設(shè)備的數(shù)據(jù)讀寫
程序視角理解IO
程序視角我們關(guān)注的則是應(yīng)用程序本身。我們知道應(yīng)用程序只有加載到內(nèi)存中作為一個進(jìn)程才能運行,它需要時刻與計算機進(jìn)行數(shù)據(jù)交換,比如讀寫磁盤、遠(yuǎn)程調(diào)用、訪問內(nèi)存等等,但是操作系統(tǒng)為了能夠正常平穩(wěn)地運行下去,它是不會運行應(yīng)用程序隨意訪問計算機硬件部分,如內(nèi)存、硬盤、網(wǎng)卡,應(yīng)用程序必須通過操作系統(tǒng)提供的API來訪問,以達(dá)到安全的訪問控制。所以應(yīng)用程序如果要訪問內(nèi)核管理的IO,則必須通過有操作系統(tǒng)提供的API來間接訪問。所以 IO對應(yīng)應(yīng)用程序而言,強調(diào)的則是 通過向內(nèi)核發(fā)起系統(tǒng)調(diào)用完成對I/O的間接訪問。

所以,換句話說應(yīng)用程序發(fā)起一次IO訪問是分為兩個階段的:
IO 調(diào)用階段:應(yīng)用程序向內(nèi)核發(fā)起系統(tǒng)調(diào)用。 IO執(zhí)行階段:內(nèi)核執(zhí)行IO操作并返回。 數(shù)據(jù)準(zhǔn)備階段:內(nèi)核等待IO設(shè)備準(zhǔn)備好數(shù)據(jù) 數(shù)據(jù)拷貝階段:將數(shù)據(jù)從內(nèi)核緩沖區(qū)拷貝到用戶空間緩沖區(qū)

用戶空間&內(nèi)核空間
操作系統(tǒng)是利用CPU 指令來計算和控制計算機系統(tǒng)的,有些指令很溫和,我們操作它不會對操作系統(tǒng)產(chǎn)生什么危害,而有些指令則非常危險,如果使用不當(dāng)則會導(dǎo)致系統(tǒng)崩潰,如果操作系統(tǒng)允許所有的應(yīng)用程序能夠直接訪問這些很危險的指令,這會讓計算機大大增加崩潰的概率。所以操作系統(tǒng)為了更加地保護(hù)自己,則將這些危險的指令保護(hù)起來,不允許應(yīng)用程序直接訪問。
現(xiàn)代操作系統(tǒng)都是采用虛擬存儲器,操作系統(tǒng)為了保護(hù)危險指令被應(yīng)用程序直接訪問,則將虛擬空間劃分為內(nèi)核空間和用戶空間。
內(nèi)核空間則是操作系統(tǒng)的核心,它提供操作系統(tǒng)的最基本的功能,是操作系統(tǒng)工作的基礎(chǔ),它負(fù)責(zé)管理系統(tǒng)的進(jìn)程、內(nèi)存、設(shè)備驅(qū)動程序、文件和網(wǎng)絡(luò)系統(tǒng),決定著系統(tǒng)的性能和穩(wěn)定性。 用戶空間,非內(nèi)核應(yīng)用程序則運行在用戶空間。用戶空間中的代碼運行在較低的特權(quán)級別上,只能看到允許它們使用的部分系統(tǒng)資源,并且不能使用某些特定的系統(tǒng)功能,也不能直接訪問內(nèi)核空間和硬件設(shè)備,以及其他一些具體的使用限制。
進(jìn)行空間劃分后,用戶空間通過操作系統(tǒng)提供的API間接訪問操作系統(tǒng)的內(nèi)核,提高了操作系統(tǒng)的穩(wěn)定性和可用性。
想要詳細(xì)了解Linux 內(nèi)核空間和用戶空間,則可以關(guān)注如下兩篇文章:
https://cloud.tencent.com/developer/article/1739264 https://developer.aliyun.com/article/297062
用戶態(tài)和內(nèi)核態(tài)進(jìn)程切換
內(nèi)核態(tài): CPU可以訪問內(nèi)存所有數(shù)據(jù), 包括外圍設(shè)備, 例如硬盤,、網(wǎng)卡,CPU也可以將自己從一個程序切換到另一個程序。 用戶態(tài): 只能受限的訪問內(nèi)存, 且不允許訪問外圍設(shè)備。占用CPU的能力被剝奪, CPU資源可以被其他程序獲取。
我們知道CPU為了保護(hù)操作系統(tǒng),將空間劃分為內(nèi)核空間和用戶空間,進(jìn)程既可以在內(nèi)核空間運行,也可以在用戶空間運行。當(dāng)進(jìn)程運行在內(nèi)核空間時,它就處在內(nèi)核態(tài),當(dāng)進(jìn)程運行在用戶空間時,他就是用戶態(tài)。開始所有應(yīng)用程序都是運行在用戶空間的,這個時候它是用戶態(tài),但是它想做一些只有內(nèi)核空間才能做的事情,如讀取IO,這個時候進(jìn)程需要通過系統(tǒng)調(diào)用來訪問內(nèi)核空間,進(jìn)程則需要從用戶態(tài)轉(zhuǎn)變?yōu)閮?nèi)核態(tài)。
用戶態(tài)和內(nèi)核態(tài)之間的切換開銷有點兒大,那它開銷在哪里呢?有如下幾點:
保留用戶態(tài)現(xiàn)場(上下文、寄存器、用戶棧等) 復(fù)制用戶態(tài)參數(shù),用戶棧切到內(nèi)核棧,進(jìn)入內(nèi)核態(tài) 額外的檢查(因為內(nèi)核代碼對用戶不信任) 執(zhí)行內(nèi)核態(tài)代碼 復(fù)制內(nèi)核態(tài)代碼執(zhí)行結(jié)果,回到用戶態(tài) 恢復(fù)用戶態(tài)現(xiàn)場(上下文、寄存器、用戶棧等)
所以,頻繁的IO操作會頻繁的造成用戶態(tài) —> 內(nèi)核態(tài) —> 用戶態(tài)的切換,這嚴(yán)重會影響系統(tǒng)性能。后面小編會介紹IO的一些優(yōu)化,重點就是減少切換。
好了,就到這里了,想要詳細(xì)了解用戶態(tài)和內(nèi)核態(tài),可以關(guān)注如下兩篇文章:
https://www.cnblogs.com/shangxiaofei/p/5567776.html https://juejin.cn/post/6920621924791894023
五種IO模型
《UNIX網(wǎng)絡(luò)編程》說得很清楚,5種IO模型分別是 阻塞IO模型、 非阻塞IO模型、 IO復(fù)用模型、 信號驅(qū)動IO模型、 異步IO模型。前4種為同步IO操作,只有異步IO模型是異步IO操作。
這里小編問個問題,為什么前四種是同步,而只有異步IO模型才是異步呢?
阻塞IO模型
阻塞IO模型是最常見最簡單的IO模型,圖如下:

應(yīng)用程序發(fā)起一個系統(tǒng)調(diào)用(recvform),這個時候應(yīng)用程序會一直阻塞下去,直到內(nèi)核把數(shù)據(jù)準(zhǔn)備好,并將其從內(nèi)核復(fù)制到用戶空間,復(fù)制完成后返回成功提示,這個時候應(yīng)用程序才會繼續(xù)處理數(shù)據(jù)。
所以,阻塞IO模型在IO兩個階段都會阻塞。
優(yōu)點 模型簡單,實現(xiàn)難度低 適用于并發(fā)量較小的應(yīng)用開發(fā) 缺點 整個過程都阻塞,進(jìn)程一直掛起,程序性能較為低,不適用并發(fā)大的應(yīng)用
場景
某天,你跟你女朋友(假如你有女朋友)去飯店吃飯,點完餐后,你就做坐那里一直等菜做好后,吃飽喝足才離開。這期間你和你女朋友由于擔(dān)心不知道菜什么時候才能做好,所以這個期間你們就只能一直在座位上面等著,什么時候也不能干。
非阻塞 IO模型
非阻塞IO模型圖例如下:

應(yīng)用程序發(fā)起recvform系統(tǒng)調(diào)用,如果數(shù)據(jù)報沒有準(zhǔn)備會則會立即返回一個EWOULDBLOCK錯誤碼,進(jìn)程并不需要進(jìn)行等待。進(jìn)程收到該錯誤后,判斷內(nèi)核數(shù)據(jù)還沒有準(zhǔn)備好,它還可以繼續(xù)發(fā)送 recvform,如果數(shù)據(jù)報已經(jīng)準(zhǔn)備好了,待數(shù)據(jù)從內(nèi)核拷貝到用戶空間返回成功指示后,進(jìn)程則可以處理數(shù)據(jù)報了,
所以, 非阻塞IO模型需要應(yīng)用進(jìn)程不斷地主動詢問內(nèi)核數(shù)據(jù)是否已準(zhǔn)備好了。
優(yōu)點 模型簡單,實現(xiàn)難度低 與阻塞IO模型對比,它在等待數(shù)據(jù)報的過程中,進(jìn)程并沒有阻塞,它可以做其他的事情 缺點 輪詢發(fā)送 recvform ,消耗CPU 資源 與阻塞IO模型一樣,它也不適用于并發(fā)量大的應(yīng)用程序
場景
一個星期后,你跟你女朋友還是去那家餐廳吃飯,點完菜后,你女朋友吸取上次教訓(xùn),知道要在這里干等,所以還不如去逛逛,買點香水口紅啥的。但是呢,由于你們擔(dān)心會錯過上菜,所以你們就每隔一段時間就來問下服務(wù)員,你們的菜準(zhǔn)備好了沒有,來來回回好多回,若干次后,終于問到菜已經(jīng)準(zhǔn)備好了,然后你們就開心的吃起來。
IO復(fù)用模型
基于非阻塞IO模型,我們知道,它需要進(jìn)程不斷地輪詢發(fā)起recvform系統(tǒng)調(diào)用,在整個過程中,輪詢會占據(jù)很大一部分過程,而且不斷輪詢是很消耗CPU的。而且我們又不是只有一個進(jìn)程在這里發(fā)起recvform系統(tǒng)低調(diào)用,有可能是幾萬幾十萬個。
我們可以想象這樣一個場景,你是喜茶服務(wù)員,每個人點好奶茶后,都會過來問你他的奶茶好了沒有,三五個人你頂?shù)米?,那幾十上百個人呢?而且他們又不是只問一次,而是每隔幾分鐘就來問你一次,就問你煩不煩?我估計你都會懷疑人生了。那有辦法解決沒有呢?這么多人來問你受不了,一個人問不就可以解決了?奶茶做好了,由他來通知你們不就可以了?
IO復(fù)用模型采用的就是這種方式,不需要所有進(jìn)程輪詢來發(fā)起recvform來查詢數(shù)據(jù)是否已經(jīng)準(zhǔn)備好了,而是有人幫忙來詢問,這個幫忙的人就是select。
IO復(fù)用模型圖例如下:

多個進(jìn)程的IO注冊到一個復(fù)用器(select)上,然后用一個進(jìn)程監(jiān)聽該 select,select 會監(jiān)聽所有注冊進(jìn)來的IO。如果內(nèi)核的數(shù)據(jù)報沒有準(zhǔn)備好,select 調(diào)用進(jìn)程會被阻塞,而當(dāng)任一IO在內(nèi)核緩沖區(qū)中有數(shù)據(jù),select調(diào)用就會返回可讀條件,然后進(jìn)程再進(jìn)行recvform系統(tǒng)調(diào)用,內(nèi)核將數(shù)據(jù)拷貝到用戶空間,注意這個過程是阻塞的。
優(yōu)點 一個進(jìn)行負(fù)責(zé)狀態(tài)監(jiān)聽,性能較好。 適用于高并發(fā)應(yīng)用程序 缺點 模型復(fù)雜,實現(xiàn)、開發(fā)難度較大
場景
還是那家餐廳,開始的時候,大家都是在那里等,服務(wù)員只需要等菜做好,端上來就可以了,某天有些小伙伴發(fā)現(xiàn)你跟你女朋友竟然利用等菜的空閑時間去逛街(雖然累,但好歹也買了幾件東西對吧),然后他們也采用了這種方式,這個時候服務(wù)員就受不了了,你們隔一段時間就來問,隔一段時間就來問,煩都煩死了,于是他想了一個辦法,說,你們派一個人來問就可以了,我這邊做了由他來告訴你們菜是否已經(jīng)做了好。
后面小編會有專門的一篇文章來分析IO多路復(fù)用,敬請期待??!
信號驅(qū)動IO模型
IO 復(fù)用模型在第一個階段和第二個階段其實都有阻塞,第一個階段阻塞于 select 調(diào)用,第二個階段阻塞于數(shù)據(jù)復(fù)制,那有沒有辦法在第一個階段或者第二個階段不阻塞,進(jìn)一步提升性能呢?信號驅(qū)動IO模型。圖例如下:

進(jìn)程發(fā)起一個IO操作,會向內(nèi)核注冊一個信號處理程序,然后 立即返回不阻塞,當(dāng)內(nèi)核將數(shù)據(jù)報準(zhǔn)備好后會發(fā)送一個信號給進(jìn)程,這時候進(jìn)程便可以在信號處理程序中調(diào)用IO處理數(shù)據(jù)報。它與IO復(fù)用模型的主要區(qū)別是等待數(shù)據(jù)階段無阻塞。
優(yōu)點 采用回調(diào)機制,等待數(shù)據(jù)階段無阻塞 適用于高并發(fā)應(yīng)用程序 缺點 模型較為復(fù)雜,實現(xiàn)起來有點兒困難
場景
有人幫你問,其實也不是那么好,因為你還是要等他來告訴你,而且他是只要你們當(dāng)中有一個人的菜做好了就告訴你們所有人。于是,你們又想了一種方案,我點完菜后,我告訴服務(wù)員,我留我的微信在你這里,菜做好后,你告訴我就可以了。這樣你女票就可以利用這個空余時間逛更久了。
異步IO模型
信號驅(qū)動IO模型,進(jìn)一步優(yōu)化了IO操作流程,經(jīng)過了三輪優(yōu)化,它終于不用在數(shù)據(jù)等待階段阻塞了,但是在數(shù)據(jù)復(fù)制節(jié)點依然是阻塞的,所以如果我們需要進(jìn)一步優(yōu)化的話,只需要把第二個階段也進(jìn)一步優(yōu)化為異步,我們就大功告成了,也就變成了真正的異步IO了。

當(dāng)進(jìn)程發(fā)送一個IO操作,進(jìn)程會立刻返回(不阻塞),但是也不能發(fā)揮結(jié)果,內(nèi)核會把整個IO數(shù)據(jù)報準(zhǔn)備好后,再通知進(jìn)程,進(jìn)程再處理數(shù)據(jù)報。
場景經(jīng)過了前面四次的
優(yōu)點 整個過程都不阻塞,一步到位 非常使用高并發(fā)應(yīng)用 缺點 需要操作系統(tǒng)的底層支持,LINUX 2.5 版本內(nèi)核首現(xiàn),2.6 版本產(chǎn)品的內(nèi)核標(biāo)準(zhǔn)特性 模型復(fù)雜,實現(xiàn)、開發(fā)難度較大
場景
雖然留電話的方式不錯,你只需要留一個微信就可以了,瞬間解放了,后面你又發(fā)現(xiàn)了一個問題,你到了餐廳后,還不能立刻吃飯,因為他們還要上菜,這個過程你還是要等,如果你到店后立刻就可以吃難道不是更爽么?所以你服務(wù)員溝通說,你做好菜后,直接上,完成后再微信通知你,你到店后就直接吃了。這樣你是不是更加爽了?
總結(jié)
五種IO模型,層層遞進(jìn),一個比一個性能高,當(dāng)然模型的復(fù)雜度也一個比一個復(fù)雜。最后用一張圖來總結(jié)下

通過這張圖,我想你應(yīng)該可以回答文章前面的那個問題了。
參考
https://cloud.tencent.com/developer/article/1684951
https://www.jianshu.com/p/486b0965c296
