轉(zhuǎn)自 http://www.tianshouzhi.com/api/tutorials/netty/221
同步異步阻塞非阻塞概念
- 同步與異步同步和異步關(guān)注的是消息通信機(jī)制 (synchronous communication/ asynchronous communication)所謂同步扇售,就是在發(fā)出一個(gè)調(diào)用時(shí)被因,在沒有得到結(jié)果之前溺职,該調(diào)用就不返回术健。但是一旦調(diào)用返回熟菲,就得到返回值了府瞄。換句話說钉答,就是由調(diào)用者主動(dòng)等待這個(gè)調(diào)用的結(jié)果例获。而異步則是相反,調(diào)用在發(fā)出之后印叁,這個(gè)調(diào)用就直接返回了被冒,所以沒有返回結(jié)果。換句話說轮蜕,當(dāng)一個(gè)異步過程調(diào)用發(fā)出后昨悼,調(diào)用者不會(huì)立刻得到結(jié)果。而是在調(diào)用發(fā)出后跃洛,被調(diào)用者通過狀態(tài)率触、通知來通知調(diào)用者,或通過回調(diào)函數(shù)處理這個(gè)調(diào)用汇竭。典型的異步編程模型比如Node.js舉個(gè)通俗的例子:你打電話問書店老板有沒有《分布式系統(tǒng)》這本書葱蝗,如果是同步通信機(jī)制,書店老板會(huì)說细燎,你稍等两曼,”我查一下",然后開始查啊查玻驻,等查好了(可能是5秒悼凑,也可能是一天)告訴你結(jié)果(返回結(jié)果)。而異步通信機(jī)制击狮,書店老板直接告訴你我查一下啊佛析,查好了打電話給你,然后直接掛電話了(不返回結(jié)果)彪蓬。然后查好了寸莫,他會(huì)主動(dòng)打電話給你。在這里老板通過“回電”這種方式來回調(diào)档冬。
- 阻塞與非阻塞阻塞和非阻塞關(guān)注的是程序在等待調(diào)用結(jié)果(消息膘茎,返回值)時(shí)的狀態(tài).阻塞調(diào)用是指調(diào)用結(jié)果返回之前,當(dāng)前線程會(huì)被掛起酷誓。調(diào)用線程只有在得到結(jié)果之后才會(huì)返回披坏。非阻塞調(diào)用指在不能立刻得到結(jié)果之前,該調(diào)用不會(huì)阻塞當(dāng)前線程盐数。還是上面的例子棒拂,你打電話問書店老板有沒有《分布式系統(tǒng)》這本書,你如果是阻塞式調(diào)用,你會(huì)一直把自己“掛起”帚屉,直到得到這本書有沒有的結(jié)果谜诫,如果是非阻塞式調(diào)用,你不管老板有沒有告訴你攻旦,你自己先一邊去玩了喻旷, 當(dāng)然你也要偶爾過幾分鐘check一下老板有沒有返回結(jié)果。在這里阻塞與非阻塞與是否同步異步無關(guān)牢屋。跟老板通過什么方式回答你結(jié)果無關(guān)且预。
IO 是主存和外部設(shè)備 ( 硬盤、終端和網(wǎng)絡(luò)等 ) 拷貝數(shù)據(jù)的過程烙无。 IO 是操作系統(tǒng)的底層功能實(shí)現(xiàn)锋谐,底層通過 I/O 指令進(jìn)行完成。在本教程中皱炉,我們所說的IO指的都是網(wǎng)絡(luò)IO怀估。
《UNIX網(wǎng)絡(luò)編程:卷一》第六章——I/O復(fù)用。書中向我們提及了5種類UNIX下可用的I/O模型:
1合搅、阻塞式I/O:blocking IO
2、非阻塞式I/O: nonblocking IO
3歧蕉、I/O復(fù)用(select灾部,poll,epoll...):IO multiplexing
4惯退、信號(hào)驅(qū)動(dòng)式I/O(SIGIO):signal driven IO
5赌髓、異步I/O(POSIX的aio_系列函數(shù)):asynchronous IO
對(duì)于這五種IO模型,Java并不是一開始就都全部支持催跪,而是有一個(gè)逐步演進(jìn)的過程:
在JDK1.4之前锁蠕,Java的IO模型只支持阻塞式IO(Blocking IO),簡(jiǎn)稱為BIO
在JDK1.4時(shí)懊蒸,支持了I/O多路復(fù)用模型荣倾,相對(duì)于之前的IO模型,這是一個(gè)新的模型骑丸,所以稱之為NIO(New IO)舌仍,有新就有舊,所以有時(shí)也把BIO稱之為OIO(old IO)通危,其實(shí)都是一個(gè)意思铸豁。到現(xiàn)在為止,JDK1.8都已經(jīng)出來了菊碟,JDK1.4時(shí)引入的nio包节芥,也沒有什么新鮮的了,所以更多的人愿意把NIO理解為None-Blocking IO逆害,即非阻塞IO头镊。
在JDK1.7時(shí)增炭,對(duì)NIO包進(jìn)行了升級(jí),支持了異步I/O(Asynchronous IO)拧晕,簡(jiǎn)稱為AIO隙姿,因?yàn)槭菍?duì)nio包的升級(jí),所有有時(shí)又稱之為NIO2.0厂捞。
理解了Java IO模型演進(jìn)與Unix五種IO模型之間的關(guān)系之后输玷,我們對(duì)這五種模型進(jìn)行詳細(xì)的介紹。
在這里靡馁,我們以一個(gè)網(wǎng)絡(luò)IO來舉例:
對(duì)于一個(gè)network IO (以read舉例)欲鹏,它會(huì)涉及到兩個(gè)系統(tǒng)對(duì)象,一個(gè)是調(diào)用這個(gè)IO的進(jìn)程臭墨,另一個(gè)就是系統(tǒng)內(nèi)核(kernel)赔嚎。當(dāng)一個(gè)read操作發(fā)生時(shí),它會(huì)經(jīng)歷兩個(gè)階段:
階段1:等待數(shù)據(jù)準(zhǔn)備 (Waiting for the data to be ready)
階段2:將數(shù)據(jù)從內(nèi)核拷貝到進(jìn)程中 (Copying the data from the kernel to the process)
如果下圖所示:
圖中明顯忽略了很多細(xì)節(jié)胧弛,僅顯示了涉及到的基本步驟 尤误,注意圖中用戶空間和內(nèi)核空間的概念。
用戶空間是常規(guī)進(jìn)程所在區(qū)域结缚。 JVM 就是常規(guī)進(jìn)程损晤,駐守于用戶空間。用戶空間是非特權(quán)區(qū)域:比如红竭,在該區(qū)域執(zhí)行的代碼就不能直接訪問硬件設(shè)備尤勋。
內(nèi)核空間是操作系統(tǒng)所在區(qū)域。內(nèi)核代碼有特別的權(quán)力:它能與設(shè)備控制器通訊茵宪,控制著用戶區(qū)域進(jìn)程的運(yùn)行狀態(tài)最冰,等等。最重要的是稀火,所有 I/O 都直接(如這里所述)或間接通過內(nèi)核空間暖哨。
當(dāng)進(jìn)程請(qǐng)求 I/O 操作的時(shí)候,它執(zhí)行一個(gè)系統(tǒng)調(diào)用將控制權(quán)移交給內(nèi)核憾股。C/C++程序員所熟知的底層函數(shù) open( )鹿蜀、 read( )、 write( )和 close( )要做的無非就是建立和執(zhí)行適當(dāng)?shù)南到y(tǒng)調(diào)用服球。當(dāng)內(nèi)核以這種方式被調(diào)用茴恰,它隨即采取任何必要步驟,找到進(jìn)程所需數(shù)據(jù)斩熊,并把數(shù)據(jù)傳送到用戶空間內(nèi)的指定緩沖區(qū)往枣。內(nèi)核試圖對(duì)數(shù)據(jù)進(jìn)行高速緩存或預(yù)讀取,因此進(jìn)程所需數(shù)據(jù)可能已經(jīng)在內(nèi)核空間里了。如果是這樣分冈,該數(shù)據(jù)只需簡(jiǎn)單地拷貝出來即可圾另。如果數(shù)據(jù)不在內(nèi)核空間,則進(jìn)程被掛起雕沉,內(nèi)核著手把數(shù)據(jù)讀進(jìn)內(nèi)存集乔。
了解了這兩個(gè)階段的作用之后,我們接下來就可以深入講解五種IO模型了坡椒,他們的區(qū)別就是在兩個(gè)階段上上有著不同的邏輯扰路。
1、Blocking IO
在linux中倔叼,默認(rèn)情況下所有的socket都是blocking汗唱,一個(gè)典型的讀操作流程大概是這樣:
第一步通常涉及等待數(shù)據(jù)從網(wǎng)絡(luò)中到達(dá)。當(dāng)所有等待數(shù)據(jù)到達(dá)時(shí)丈攒,它被復(fù)制到內(nèi)核中的某個(gè)緩沖區(qū)哩罪。
第二步就是把數(shù)據(jù)從內(nèi)核緩沖區(qū)復(fù)制到應(yīng)用程序緩沖區(qū)。
當(dāng)用戶進(jìn)程調(diào)用了recvfrom這個(gè)系統(tǒng)調(diào)用巡验,kernel就開始了IO的第一個(gè)階段:準(zhǔn)備數(shù)據(jù)际插。對(duì)于network io來說,很多時(shí)候數(shù)據(jù)在一開始還沒有到達(dá)(比如深碱,還沒有收到一個(gè)完整的UDP包)腹鹉,這個(gè)時(shí)候kernel就要等待足夠的數(shù)據(jù)到來。而在用戶進(jìn)程這邊敷硅,整 個(gè)進(jìn)程會(huì)被阻塞。當(dāng)kernel一直等到數(shù)據(jù)準(zhǔn)備好了愉阎,它就會(huì)將數(shù)據(jù)從kernel中拷貝到用戶內(nèi)存绞蹦,然后kernel返回結(jié)果,用戶進(jìn)程才解除 block的狀態(tài)榜旦,重新運(yùn)行起來幽七。
所以,blocking IO的特點(diǎn)就是在IO執(zhí)行的兩個(gè)階段都被block了溅呢。
2澡屡、非阻塞式I/O
linux下,可以通過設(shè)置socket使其變?yōu)閚on-blocking咐旧。當(dāng)對(duì)一個(gè)non-blocking socket執(zhí)行讀操作時(shí)驶鹉,流程是這個(gè)樣子:
從圖中可以看出,當(dāng)用戶進(jìn)程發(fā)出read操作時(shí)铣墨,如果kernel中的數(shù)據(jù)還沒有準(zhǔn)備好室埋,那么它并不會(huì)block用戶進(jìn)程,而是立刻返回一個(gè)error。 從用戶進(jìn)程角度講 姚淆,它發(fā)起一個(gè)read操作后孕蝉,并不需要等待,而是馬上就得到了一個(gè)結(jié)果腌逢。用戶進(jìn)程判斷結(jié)果是一個(gè)error時(shí)降淮,它就知道數(shù)據(jù)還沒有準(zhǔn)備好,于是它可以再次 發(fā)送read操作搏讶。一旦kernel中的數(shù)據(jù)準(zhǔn)備好了佳鳖,并且又再次收到了用戶進(jìn)程的system call,那么它馬上就將數(shù)據(jù)拷貝到了用戶內(nèi)存窍蓝,然后返回腋颠。
所以,用戶進(jìn)程第一個(gè)階段不是阻塞的,需要不斷的主動(dòng)詢問kernel數(shù)據(jù)好了沒有吓笙;第二個(gè)階段依然總是阻塞的淑玫。
3、I/O多路復(fù)用
IO multiplexing這個(gè)詞可能有點(diǎn)陌生面睛,但是如果我說select絮蒿,epoll,大概就都能明白了叁鉴。有些地方也稱這種IO方式為event driven IO土涝。我們都知道,select/epoll的好處就在于單個(gè)process就可以同時(shí)處理多個(gè)網(wǎng)絡(luò)連接的IO幌墓。
IO復(fù)用同非阻塞IO本質(zhì)一樣但壮,不過利用了新的select系統(tǒng)調(diào)用,由內(nèi)核來負(fù)責(zé)本來是請(qǐng)求進(jìn)程該做的輪詢操作常侣±看似比非阻塞IO還多了一個(gè)系統(tǒng)調(diào)用開銷,不過因?yàn)榭梢灾С侄嗦稩O胳施,才算提高了效率溯祸。
它的基本原理就是select /epoll這個(gè)function會(huì)不斷的輪詢所負(fù)責(zé)的所有socket,當(dāng)某個(gè)socket有數(shù)據(jù)到達(dá)了舞肆,就通知用戶進(jìn)程焦辅。它的流程如圖:
當(dāng)用戶進(jìn)程調(diào)用了select
,那么整個(gè)進(jìn)程會(huì)被block椿胯,而同時(shí)筷登,kernel會(huì)“監(jiān)視”所有select負(fù)責(zé)的socket,當(dāng)任何一個(gè) socket中的數(shù)據(jù)準(zhǔn)備好了压状,select就會(huì)返回仆抵。這個(gè)時(shí)候用戶進(jìn)程再調(diào)用read操作跟继,將數(shù)據(jù)從kernel拷貝到用戶進(jìn)程。
這個(gè)圖和blocking IO的圖其實(shí)并沒有太大的不同镣丑,事實(shí)上舔糖,還更差一些。因?yàn)檫@里需要使用兩個(gè)system call (select 和 recvfrom)莺匠,而blocking IO只調(diào)用了一個(gè)system call (recvfrom)金吗。但是,用select的優(yōu)勢(shì)在于它可以同時(shí)處理多個(gè)connection趣竣。(多說一句摇庙。所以,如果處理的連接數(shù)不是很高的話遥缕,使用 select/epoll的web server不一定比使用multi-threading + blocking IO的web server性能更好卫袒,可能延遲還更大。
select/epoll的優(yōu)勢(shì)并不是對(duì)于單個(gè)連接能處理得更快单匣,而是在于能處理更多的連接夕凝。
在IO multiplexing Model中,實(shí)際中户秤,對(duì)于每一個(gè)socket码秉,一般都設(shè)置成為non-blocking,但是鸡号,如上圖所示转砖,整個(gè)用戶的process其實(shí)是一直被 block的。只不過process是被select這個(gè)函數(shù)block鲸伴,而不是被socket IO給block府蔗。
4、信號(hào)驅(qū)動(dòng)式I/O
用的很少汞窗,就不做講解了礁竞。直接上圖
5、異步I/O
這類函數(shù)的工作機(jī)制是告知內(nèi)核啟動(dòng)某個(gè)操作杉辙,并讓內(nèi)核在整個(gè)操作(包括將數(shù)據(jù)從內(nèi)核拷貝到用戶空間)完成后通知我們。如圖:
用戶進(jìn)程發(fā)起read操作之后捶朵,立刻就可以開始去做其它的事蜘矢。而另一方面,從kernel的角度综看,當(dāng)它受到一個(gè)asynchronous read之后品腹,首先它會(huì)立刻返回,所以不會(huì)對(duì)用戶進(jìn)程產(chǎn)生任何block红碑。然后舞吭,kernel會(huì)等待數(shù)據(jù)準(zhǔn)備完成泡垃,然后將數(shù)據(jù)拷貝到用戶內(nèi)存,當(dāng)這一切都 完成之后羡鸥,kernel會(huì)給用戶進(jìn)程發(fā)送一個(gè)signal蔑穴,告訴它read操作完成了。 在這整個(gè)過程中惧浴,進(jìn)程完全沒有被block存和。
總結(jié):
其實(shí)前四種I/O模型都是同步I/O操作,他們的區(qū)別在于第一階段衷旅,而他們的第二階段是一樣的:在數(shù)據(jù)從內(nèi)核復(fù)制到應(yīng)用緩沖區(qū)期間(用戶空間)捐腿,進(jìn)程阻塞于recvfrom調(diào)用。
有人可能會(huì)說释树,non-blocking IO并沒有被block啊容燕。這里有個(gè)非尘判悖“狡猾”的地方,定義中所指的”IO operation”是指真實(shí)的IO操作宪祥,就是例子中的recvfrom這個(gè)system call。non-blocking IO在執(zhí)行recvfrom這個(gè)system call的時(shí)候猪钮,如果kernel的數(shù)據(jù)沒有準(zhǔn)備好品山,這時(shí)候不會(huì)block進(jìn)程。但是烤低,當(dāng)kernel中數(shù)據(jù)準(zhǔn)備好的時(shí)候肘交,recvfrom會(huì)將數(shù)據(jù)從 kernel拷貝到用戶內(nèi)存中,這個(gè)時(shí)候進(jìn)程是被block了扑馁,在這段時(shí)間內(nèi)涯呻,進(jìn)程是被block的。