目的##
為后期學(xué)習(xí) Netty框架打好理論基礎(chǔ),并且在分布式RPC 服務(wù)中對客戶端與服務(wù)端之間服務(wù)的調(diào)用,底層數(shù)據(jù)通訊可以使用Netty 進(jìn)行封裝。
記錄結(jié)構(gòu)##
- Java NIO(一)--I/O模型: 阻塞、非阻塞、I/O復(fù)用、同步、異步
地址:http://www.itdecent.cn/writer#/notebooks/5970279/notes/7531041/preview - Java NIO(二)--Channel、Buffer、Selector
地址:待定 - Java NIO(三)--多路復(fù)用之TCP傳輸中的NIO應(yīng)用
地址:待定 - 福利彩蛋
今天記錄I/O模型中的阻塞、非阻塞、I/O復(fù)用、同步、異步。
混沌的概念##
對于上述四種概念,常常使我陷入混沌,我經(jīng)常這樣想同步不就是阻塞的么?異步不就是非阻塞的么?
我也會(huì)去看下別人寫的博客以驗(yàn)證我的想法是正確的,事實(shí)上,大多博客也這樣舉例子:同步類似于你叫某人吃飯,某人不應(yīng)答你,你就一直等著他,這期間你什么事情都不能做,也就是說你在等待某人去吃飯這個(gè)時(shí)刻內(nèi)一直都是阻塞的。
針對于上述的舉例,我一直深信不疑,一句話,沒毛病。
但直到我看到UNIX 網(wǎng)絡(luò)編程之后,才發(fā)現(xiàn)理解有偏差,起碼我之前的理解是將I/O操作中的概念結(jié)合起來記憶。即:同步==阻塞 異步==非阻塞。
但其實(shí),以上的記憶有點(diǎn)以偏概全,或者說根本沒有清晰的認(rèn)識。
話不多說,開始記錄。
1. 明確I/O考察的對象和流程##
1.1 參考Unix網(wǎng)絡(luò)編程,一個(gè)輸入操作通常包括兩個(gè)不同的階段:
- 等待數(shù)據(jù)準(zhǔn)備好;
- 從內(nèi)核向進(jìn)程復(fù)制數(shù)據(jù)。
1.2 對于一個(gè)套接字的輸入操作:
- 通常涉及等待數(shù)據(jù)從網(wǎng)絡(luò)到達(dá),當(dāng)所等待分組到達(dá)時(shí),被復(fù)制到內(nèi)核的某個(gè)緩沖區(qū);
- 把數(shù)據(jù)從內(nèi)核緩沖區(qū)復(fù)制到應(yīng)用進(jìn)程緩沖區(qū)。
注意: 理解上述兩個(gè)不同階段對于后續(xù)理解I/O模型尤其是非阻塞I/O與同步I/O關(guān)系十分必要。
2. I/O模型##
2.1 阻塞式I/O模型
阻塞式I/O是最流行的I/O,也是所有套接字默認(rèn)的I/O。Java BIO中對socket 網(wǎng)絡(luò)數(shù)據(jù)通信的
封裝 就采用的是這種方式。當(dāng)然效率也是低下的。
阻塞式I/O是最流行的I/O,也是所有套接字默認(rèn)的I/O。

(注:所有圖片來源 Unix網(wǎng)絡(luò)編程卷1,第三版)
如圖所示,進(jìn)程調(diào)用recvfrom系統(tǒng)調(diào)用,直到網(wǎng)絡(luò)數(shù)據(jù)報(bào)到達(dá)且被復(fù)制到應(yīng)用進(jìn)程緩沖區(qū)中或發(fā)生錯(cuò)誤才返回。
注意:也就是說,進(jìn)程從調(diào)用recvfrom開始到返回的整個(gè)時(shí)段都是阻塞的(上述1.1兩個(gè)階段都是阻塞),recvfrom成功返回后,應(yīng)用進(jìn)程才開始處理數(shù)據(jù)報(bào)。
上述的注意讀三遍
2.2 非阻塞I/O模型
直接上圖:

如圖所示,不同于阻塞式I/O,非阻塞I/O在第一階段數(shù)據(jù)沒有準(zhǔn)備好的時(shí)候,不阻塞,而是直接返回一個(gè)錯(cuò)誤(EWOULDBLOCK)。
所以一般采用輪詢(polling)的方式,應(yīng)用進(jìn)程持續(xù)輪詢內(nèi)核,查看數(shù)據(jù)是否準(zhǔn)備好。當(dāng)數(shù)據(jù)準(zhǔn)備好時(shí),被復(fù)制到應(yīng)用進(jìn)程緩沖區(qū)(第二階段)。
注意:值得注意的一點(diǎn)是,當(dāng)?shù)谝浑A段數(shù)據(jù)準(zhǔn)備完成后,進(jìn)入第二階段,內(nèi)核向內(nèi)存的復(fù)制。這一階段仍然是阻塞的,這對于后續(xù)理解非阻塞與同步的關(guān)系十分重要。
上述注意項(xiàng)讀三遍。
2.3 I/O多路復(fù)用模型
I/O復(fù)用最常見的就是select和epoll,其阻塞發(fā)生在上述兩個(gè)系統(tǒng)調(diào)用之一,而不是真正的I/O系統(tǒng)調(diào)用上。 Java NIO 對TCP 網(wǎng)絡(luò)通信的封裝內(nèi)部采用的就是這種原理。

當(dāng)用戶進(jìn)程調(diào)用了select,那么整個(gè)進(jìn)程會(huì)被阻塞與select。內(nèi)核會(huì)“監(jiān)視”所有select負(fù)責(zé)的套接字,當(dāng)任何一個(gè)套接字中的數(shù)據(jù)準(zhǔn)備好了,select就會(huì)返回。(進(jìn)程阻塞)
這時(shí)候進(jìn)入第二階段,完成內(nèi)核向內(nèi)存的數(shù)據(jù)復(fù)制。(進(jìn)程阻塞)
注意:I/O復(fù)用的優(yōu)勢在于同時(shí)等待多個(gè)描述符就緒,單就一個(gè)描述符可言,其沒有優(yōu)勢,反而還會(huì)因?yàn)槎嘁淮蝧elect系統(tǒng)調(diào)用存在劣勢。
上述注意項(xiàng)讀三遍。
2.4 異步I/O模型
異步I/O的工作機(jī)制是告知內(nèi)核啟動(dòng)某個(gè)操作,并讓內(nèi)核在整個(gè)操作(包括第二階段數(shù)據(jù)從內(nèi)核向內(nèi)存的復(fù)制)完成后告知我們。
如下圖所示:

注意:異步I/O要通過調(diào)用特殊API實(shí)現(xiàn)(如POSIX的aio_read),可以看出,其在兩個(gè)階段都是沒有對于用戶進(jìn)程的阻塞的,依靠信號通知進(jìn)程整個(gè)過程完成。
上述注意讀三遍
2.5 同步、異步與阻塞、非阻塞、I/O復(fù)用的關(guān)系
在了解了阻塞式I/O、非阻塞式I/O、I/O多路復(fù)用、異步I/O后我們看下這幾個(gè)模式的I/O模型與同步異步模型有什么關(guān)系。
注意:重頭戲,接下來就是徹底領(lǐng)悟這幾個(gè)概念之間關(guān)系,讓你不再混沌,請保持接受狀態(tài),保持信心看下去。
首先先來再明確一下同步、異步I/O之間的區(qū)別。
書中所述,POSIX把兩種術(shù)語定義如下:
同步I/O:導(dǎo)致請求進(jìn)程阻塞,直到I/O操作完成;(兩個(gè)階段(等待網(wǎng)絡(luò)數(shù)據(jù)到達(dá)內(nèi)核空間緩存區(qū)域,以及將內(nèi)核空間緩存區(qū)域中的數(shù)據(jù)復(fù)制到用戶進(jìn)程緩存中)中只要有一個(gè)階段阻塞,那整個(gè)I/O操作就就是同步)
異步I/O:不導(dǎo)致請求進(jìn)程阻塞。 (兩個(gè)階段都不阻塞,那么就是異步I/O)
注意:所以說,阻塞式I/O, 非阻塞I/O, I/O復(fù)用由于都導(dǎo)致了請求進(jìn)程阻塞,所以均屬于同步I/O。
(值得注意的是非阻塞I/O,正如之前提示要注意的,其在第二階段內(nèi)核向內(nèi)存復(fù)制數(shù)據(jù)是會(huì)導(dǎo)致用戶進(jìn)程的阻塞,所以也屬于同步I/O
上述注意讀三遍
3. 總結(jié)
如下圖所示:(暫時(shí)忽略信號驅(qū)動(dòng)I/O)

可以看出阻塞式、非阻塞式、與I/O復(fù)用,其不同之處在于第一階段,第二階段的處理方式相同(均阻塞與recvfrom調(diào)用),這也是剛才說到的將他們歸于同步I/O的原因。
注意:異步I/O不存在請求進(jìn)程阻塞的情況。同時(shí)注意前三種I/O模型在第一階段的處理方式(阻塞,返回+輪詢,阻塞于select等),區(qū)分這三種I/O模型。
上述注意讀三遍
關(guān)于公眾號
精進(jìn)!
道友們,你們好。早前個(gè)人就有開設(shè)公眾號的念想,今年10月終于開搞了。
我的個(gè)人的 訂閱號--T客來了;
平時(shí)自己會(huì)總結(jié)一些后端開發(fā)相關(guān)的技術(shù);
最近也迷上了音視頻開發(fā)相關(guān)技術(shù);
技術(shù)分享包括:
- 1.FFmpeg 工程實(shí)戰(zhàn)、
- 2.數(shù)據(jù)庫 MySQL原理與實(shí)戰(zhàn)、
- 3.Redis中間件、
- 4.Nginx、Java并發(fā)編程、
-
5.Go語言方面的技術(shù)知識與實(shí)操;
T客來了
點(diǎn)擊微信圖標(biāo),掃碼就可以添加哦~
完。
