轉(zhuǎn)自: http://www.lxweimin.com/p/486b0965c296? ? http://www.lxweimin.com/p/486b0965c296
轉(zhuǎn)自 簡(jiǎn)書(shū)作者?陶邦仁?的文章,并讀后有感:感覺(jué)作者上面鏈接的兩篇文章放到一處更好理解一些,所以做了復(fù)制.自覺(jué)后面異步的部分應(yīng)該把異步的現(xiàn)狀說(shuō)一下,以便讓人有充分的了解. 文中加了一些PS,是我自己的理解,如有誤導(dǎo),請(qǐng)忽略之......
聊聊同步、異步、阻塞與非阻塞
近來(lái)遇到了一些常見(jiàn)的概念,尤其是網(wǎng)絡(luò)編程方面的概念,如:阻塞、非阻塞、異步I/O等等,對(duì)于這些概念自己也沒(méi)有太清晰的認(rèn)識(shí),只是很模糊的概念,說(shuō)了解吧也了解,但是要讓自己準(zhǔn)確的描述概念方面的具體細(xì)節(jié),卻說(shuō)的不那么準(zhǔn)確,這也是自己在這幾個(gè)方面也沒(méi)有細(xì)細(xì)考究過(guò)的原因吧。經(jīng)過(guò)看了些這幾個(gè)概念的資料,發(fā)現(xiàn)同步、異步、阻塞、非阻塞的概念其實(shí)也并不難以理解,在此寫(xiě)下此文,歡迎拍磚,希望多多交流。
1同步與異步
首先來(lái)解釋同步和異步的概念,這兩個(gè)概念與消息的通知機(jī)制有關(guān)。也就是同步與異步主要是從消息通知機(jī)制角度來(lái)說(shuō)的。
1.1概念描述
所謂同步就是一個(gè)任務(wù)的完成需要依賴另外一個(gè)任務(wù)時(shí),只有等待被依賴的任務(wù)完成后,依賴的任務(wù)才能算完成,這是一種可靠的任務(wù)序列。要么成功都成功,失敗都失敗,兩個(gè)任務(wù)的狀態(tài)可以保持一致。所謂異步是不需要等待被依賴的任務(wù)完成,只是通知被依賴的任務(wù)要完成什么工作,依賴的任務(wù)也立即執(zhí)行,只要自己完成了整個(gè)任務(wù)就算完成了。至于被依賴的任務(wù)最終是否真正完成,依賴它的任務(wù)無(wú)法確定,所以它是不可靠的任務(wù)序列。
(PS:只有兩個(gè)以及兩個(gè)以上的任務(wù)才可能會(huì)有同步/異步的概念產(chǎn)生)
1.2消息通知
異步的概念和同步相對(duì)。當(dāng)一個(gè)同步調(diào)用發(fā)出后,調(diào)用者要一直等待返回消息(結(jié)果)通知后,才能進(jìn)行后續(xù)的執(zhí)行;當(dāng)一個(gè)異步過(guò)程調(diào)用發(fā)出后,調(diào)用者不能立刻得到返回消息(結(jié)果)。實(shí)際處理這個(gè)調(diào)用的部件在完成后,通過(guò)狀態(tài)、通知和回調(diào)來(lái)通知調(diào)用者。這里提到執(zhí)行部件和調(diào)用者通過(guò)三種途徑返回結(jié)果:狀態(tài)、通知和回調(diào)。使用哪一種通知機(jī)制,依賴于執(zhí)行部件的實(shí)現(xiàn),除非執(zhí)行部件提供多種選擇,否則不受調(diào)用者控制。
如果執(zhí)行部件用狀態(tài)來(lái)通知,那么調(diào)用者就需要每隔一定時(shí)間檢查一次,效率就很低(有些初學(xué)多線程編程的人,總喜歡用一個(gè)循環(huán)去檢查某個(gè)變量的值,這其實(shí)是一種很嚴(yán)重的錯(cuò)誤);
如果是使用通知的方式,效率則很高,因?yàn)閳?zhí)行部件幾乎不需要做額外的操作。至于回調(diào)函數(shù),其實(shí)和通知沒(méi)太多區(qū)別。
1.2場(chǎng)景比喻
舉個(gè)例子,比如我去銀行辦理業(yè)務(wù),可能會(huì)有兩種方式:
選擇排隊(duì)等候;
另種選擇取一個(gè)小紙條上面有我的號(hào)碼,等到排到我這一號(hào)時(shí)由柜臺(tái)的人通知我輪到我去辦理業(yè)務(wù)了;
第一種:前者(排隊(duì)等候)就是同步等待消息通知,也就是我要一直在等待銀行辦理業(yè)務(wù)情況;第二種:后者(等待別人通知)就是異步等待消息通知。在異步消息處理中,等待消息通知者(在這個(gè)例子中就是等待辦理業(yè)務(wù)的人)往往注冊(cè)一個(gè)回調(diào)機(jī)制,在所等待的事件被觸發(fā)時(shí)由觸發(fā)機(jī)制(在這里是柜臺(tái)的人)通過(guò)某種機(jī)制(在這里是寫(xiě)在小紙條上的號(hào)碼,喊號(hào))找到等待該事件的人。阻塞非阻塞指的是一個(gè)主體,同步非同步指的是兩個(gè)以上的主體.
2阻塞與非阻塞
阻塞和非阻塞這兩個(gè)概念與程序(線程)等待消息通知(無(wú)所謂同步或者異步)時(shí)的狀態(tài)有關(guān)。也就是說(shuō)阻塞與非阻塞主要是程序(線程)等待消息通知時(shí)的狀態(tài)角度來(lái)說(shuō)的。
2.1概念描述
阻塞調(diào)用是指調(diào)用結(jié)果返回之前,當(dāng)前線程會(huì)被掛起,一直處于等待消息通知,不能夠執(zhí)行其他業(yè)務(wù)。函數(shù)只有在得到結(jié)果之后才會(huì)返回。有人也許會(huì)把阻塞調(diào)用和同步調(diào)用等同起來(lái),實(shí)際上它們是不同的。
對(duì)于同步調(diào)用來(lái)說(shuō),很多時(shí)候當(dāng)前線程可能還是激活的,只是從邏輯上當(dāng)前函數(shù)沒(méi)有返回而已,此時(shí),這個(gè)線程可能也會(huì)處理其他的消息。還有一點(diǎn),在這里先擴(kuò)展下:(a)如果這個(gè)線程在等待當(dāng)前函數(shù)返回時(shí),仍在執(zhí)行其他消息處理,那這種情況就叫做同步非阻塞;(b)如果這個(gè)線程在等待當(dāng)前函數(shù)返回時(shí),沒(méi)有執(zhí)行其他消息處理,而是處于掛起等待狀態(tài),那這種情況就叫做同步阻塞;所以同步的實(shí)現(xiàn)方式會(huì)有兩種:同步阻塞、同步非阻塞;同理,異步也會(huì)有兩種實(shí)現(xiàn):異步阻塞、異步非阻塞;
對(duì)于阻塞調(diào)用來(lái)說(shuō),則當(dāng)前線程就會(huì)被掛起等待當(dāng)前函數(shù)返回;
非阻塞和阻塞的概念相對(duì)應(yīng),指在不能立刻得到結(jié)果之前,該函數(shù)不會(huì)阻塞當(dāng)前線程,而會(huì)立刻返回。雖然表面上看非阻塞的方式可以明顯的提高CPU的利用率,但是也帶了另外一種后果就是系統(tǒng)的線程切換增加(PS:??)。增加的CPU執(zhí)行時(shí)間能不能補(bǔ)償系統(tǒng)的切換成本需要好好評(píng)估。
2.2場(chǎng)景比喻
繼續(xù)上面的那個(gè)例子,不論是排隊(duì)還是使用號(hào)碼等待通知,如果在這個(gè)等待的過(guò)程中,等待者除了等待消息通知之外不能做其它的事情,那么該機(jī)制就是阻塞的,表現(xiàn)在程序中,也就是該程序一直阻塞在該函數(shù)調(diào)用處不能繼續(xù)往下執(zhí)行。相反,有的人喜歡在銀行辦理這些業(yè)務(wù)的時(shí)候一邊打打電話發(fā)發(fā)短信一邊等待,這樣的狀態(tài)就是非阻塞的,因?yàn)樗?等待者)沒(méi)有阻塞在這個(gè)消息通知上,而是一邊做自己的事情一邊等待。但是需要注意了,同步非阻塞形式實(shí)際上是效率低下的,想象一下你一邊打著電話一邊還需要抬頭看到底隊(duì)伍排到你了沒(méi)有。如果把打電話和觀察排隊(duì)的位置看成是程序的兩個(gè)操作的話,這個(gè)程序需要在這兩種不同的行為之間來(lái)回的切換,效率可想而知是低下的;而異步非阻塞形式卻沒(méi)有這樣的問(wèn)題,因?yàn)榇螂娫捠悄?等待者)的事情,而通知你則是柜臺(tái)(消息觸發(fā)機(jī)制)的事情,程序沒(méi)有在兩種不同的操作中來(lái)回切換。
3同步/異步與阻塞/非阻塞
同步阻塞形式效率是最低的,拿上面的例子來(lái)說(shuō),就是你專心排隊(duì),什么別的事都不做。實(shí)際程序中:就是未對(duì)fd設(shè)置O_NONBLOCK標(biāo)志位的read/write操作;
異步阻塞形式如果在銀行等待辦理業(yè)務(wù)的人采用的是異步的方式去等待消息被觸發(fā)(通知),也就是領(lǐng)了一張小紙條,假如在這段時(shí)間里他不能離開(kāi)銀行做其它的事情,那么很顯然,這個(gè)人被阻塞在了這個(gè)等待的操作上面;異步操作是可以被阻塞住的,只不過(guò)它不是在處理消息時(shí)阻塞,而是在等待消息通知時(shí)被阻塞。比如select函數(shù),假如傳入的最后一個(gè)timeout參數(shù)為NULL,那么如果所關(guān)注的事件沒(méi)有一個(gè)被觸發(fā),程序就會(huì)一直阻塞在這個(gè)select調(diào)用處。
同步非阻塞形式實(shí)際上是效率低下的,想象一下你一邊打著電話一邊還需要抬頭看到底隊(duì)伍排到你了沒(méi)有,如果把打電話和觀察排隊(duì)的位置看成是程序的兩個(gè)操作的話,這個(gè)程序需要在這兩種不同的行為之間來(lái)回的切換,效率可想而知是低下的。很多人會(huì)寫(xiě)阻塞的read/write操作,但是別忘了可以對(duì)fd設(shè)置O_NONBLOCK標(biāo)志位,這樣就可以將同步操作變成非阻塞的了。
異步非阻塞形式效率更高,因?yàn)榇螂娫捠悄?等待者)的事情,而通知你則是柜臺(tái)(消息觸發(fā)機(jī)制)的事情,程序沒(méi)有在兩種不同的操作中來(lái)回切換。比如說(shuō),這個(gè)人突然發(fā)覺(jué)自己煙癮犯了,需要出去抽根煙,于是他告訴大堂經(jīng)理說(shuō),排到我這個(gè)號(hào)碼的時(shí)候麻煩到外面通知我一下(注冊(cè)一個(gè)回調(diào)函數(shù)),那么他就沒(méi)有被阻塞在這個(gè)等待的操作上面,自然這個(gè)就是異步+非阻塞的方式了。如果使用異步非阻塞的情況,比如aio_*組的操作,當(dāng)發(fā)起一個(gè)aio_read操作時(shí),函數(shù)會(huì)馬上返回不會(huì)被阻塞,當(dāng)所關(guān)注的事件被觸發(fā)時(shí)會(huì)調(diào)用之前注冊(cè)的回調(diào)函數(shù)進(jìn)行處理。
很多人會(huì)把同步和阻塞混淆,我想是因?yàn)楹芏鄷r(shí)候同步操作會(huì)以阻塞的形式表現(xiàn)出來(lái),比如很多人會(huì)寫(xiě)阻塞的read/write操作,但是別忘了可以對(duì)fd設(shè)置O_NONBLOCK標(biāo)志位,這樣就可以將同步操作變成非阻塞的了。但最根本是因?yàn)闆](méi)有區(qū)分這兩個(gè)概念,比如阻塞的read/write操作中,其實(shí)是把消息通知機(jī)制和等待消息通知的狀態(tài)結(jié)合在了一起,在這里所關(guān)注的消息就是fd是否可讀/寫(xiě),而等待消息通知的狀態(tài)則是對(duì)fd可讀/寫(xiě)等待過(guò)程中程序(線程)的狀態(tài)。當(dāng)我們將這個(gè)fd設(shè)置為非阻塞的時(shí)候,read/write操作就不會(huì)在等待消息通知這里阻塞,如果fd不可讀/寫(xiě)則操作立即返回。同樣的,很多人也會(huì)把異步和非阻塞混淆,因?yàn)楫惒讲僮饕话愣疾粫?huì)在真正的IO操作處被阻塞,比如如果用select函數(shù),當(dāng)select返回可讀時(shí)再去read一般都不會(huì)被阻塞,而是在select函數(shù)調(diào)用處阻塞。4小明的故事對(duì)上面所講的概念再次進(jìn)行一個(gè)場(chǎng)景梳理,上面已經(jīng)明確說(shuō)明,同步/異步關(guān)注的是消息通知的機(jī)制,而阻塞/非阻塞關(guān)注的是程序(線程)等待消息通知時(shí)的狀態(tài)。以小明下載文件打個(gè)比方,從這兩個(gè)關(guān)注點(diǎn)來(lái)再次說(shuō)明這兩組概念,希望能夠更好的促進(jìn)大家的理解。
同步阻塞:小明一直盯著下載進(jìn)度條,到100%的時(shí)候就完成。同步體現(xiàn)在:等待下載完成通知;阻塞體現(xiàn)在:等待下載完成通知過(guò)程中,不能做其他任務(wù)處理;
同步非阻塞:小明提交下載任務(wù)后就去干別的,每過(guò)一段時(shí)間就去瞄一眼進(jìn)度條,看到100%就完成。同步體現(xiàn)在:等待下載完成通知;非阻塞體現(xiàn)在:等待下載完成通知過(guò)程中,去干別的任務(wù)了,只是時(shí)不時(shí)會(huì)瞄一眼進(jìn)度條;【小明必須要在兩個(gè)任務(wù)間切換,關(guān)注下載進(jìn)度】
異步阻塞:小明換了個(gè)有下載完成通知功能的軟件,下載完成就“叮”一聲。不過(guò)小明仍然一直等待“叮”的聲音(看起來(lái)很傻,不是嗎)。異步體現(xiàn)在:下載完成“叮”一聲通知;阻塞體現(xiàn)在:等待下載完成“叮”一聲通知過(guò)程中,不能做其他任務(wù)處理;
異步非阻塞:仍然是那個(gè)會(huì)“叮”一聲的下載軟件,小明提交下載任務(wù)后就去干別的,聽(tīng)到“叮”的一聲就知道完成了。異步體現(xiàn)在:下載完成“叮”一聲通知;非阻塞體現(xiàn)在:等待下載完成“叮”一聲通知過(guò)程中,去干別的任務(wù)了,只需要接收“叮”聲通知即可;【軟件處理下載任務(wù),小明處理其他任務(wù),不需關(guān)注進(jìn)度,只需接收軟件“叮”聲通知,即可】
也就是說(shuō),同步/異步是“下載完成消息”通知的方式(機(jī)制),而阻塞/非阻塞則是在等待“下載完成消息”通知過(guò)程中的狀態(tài)(能不能干其他任務(wù)),在不同的場(chǎng)景下,同步/異步、阻塞/非阻塞的四種組合都有應(yīng)用。所以,綜上所述,同步和異步僅僅是關(guān)注的消息如何通知的機(jī)制,而阻塞與非阻塞關(guān)注的是等待消息通知時(shí)的狀態(tài)。也就是說(shuō),同步的情況下,是由處理消息者自己去等待消息是否被觸發(fā),而異步的情況下是由觸發(fā)機(jī)制來(lái)通知處理消息者,所以在異步機(jī)制中,處理消息者和觸發(fā)機(jī)制之間就需要一個(gè)連接的橋梁:在銀行的例子中,這個(gè)橋梁就是小紙條上面的號(hào)碼。在小明的例子中,這個(gè)橋梁就是軟件“叮”的聲音。最后,請(qǐng)大家注意理解“消息通知機(jī)制”和“等待消息通知時(shí)的狀態(tài)”這兩個(gè)概念,這是理解四個(gè)概念的關(guān)鍵所在。
聊聊Linux五種IO模型
上一篇《聊聊同步、異步、阻塞與非阻塞》已經(jīng)通俗的講解了,要理解同步、異步、阻塞與非阻塞重要的兩個(gè)概念點(diǎn)了,沒(méi)有看過(guò)的,建議先看這篇博文理解這兩個(gè)概念點(diǎn)。在認(rèn)知上,建立統(tǒng)一的模型。這樣,大家在繼續(xù)看本篇時(shí),才不會(huì)理解有偏差。那么,在正式開(kāi)始講Linux IO模型前,比如:同步IO和異步IO,阻塞IO和非阻塞IO分別是什么,到底有什么區(qū)別?不同的人在不同的上下文下給出的答案是不同的。所以先限定一下本文的上下文。
1概念說(shuō)明
在進(jìn)行解釋之前,首先要說(shuō)明幾個(gè)概念:
1.1 用戶空間和內(nèi)核空間
1.2 進(jìn)程切換
1.3 進(jìn)程的阻塞
1.4 文件描述符
1.5 緩存IO
1.1用戶空間與內(nèi)核空間
現(xiàn)在操作系統(tǒng)都是采用虛擬存儲(chǔ)器,那么對(duì)32位操作系統(tǒng)而言,它的尋址空間(虛擬存儲(chǔ)空間)為4G(2的32次方)。操作系統(tǒng)的核心是內(nèi)核,獨(dú)立于普通的應(yīng)用程序,可以訪問(wèn)受保護(hù)的內(nèi)存空間,也有訪問(wèn)底層硬件設(shè)備的所有權(quán)限。為了保證用戶進(jìn)程不能直接操作內(nèi)核(kernel),保證內(nèi)核的安全,操作系統(tǒng)將虛擬空間劃分為兩部分,一部分為內(nèi)核空間,一部分為用戶空間。針對(duì)linux操作系統(tǒng)而言,將最高的1G字節(jié)(從虛擬地址0xC0000000到0xFFFFFFFF),供內(nèi)核使用,稱為內(nèi)核空間,而將較低的3G字節(jié)(從虛擬地址0x00000000到0xBFFFFFFF),供各個(gè)進(jìn)程使用,稱為用戶空間。
1.2進(jìn)程切換
為了控制進(jìn)程的執(zhí)行,內(nèi)核必須有能力掛起正在CPU上運(yùn)行的進(jìn)程,并恢復(fù)以前掛起的某個(gè)進(jìn)程的執(zhí)行。這種行為被稱為進(jìn)程切換。因此可以說(shuō),任何進(jìn)程都是在操作系統(tǒng)內(nèi)核的支持下運(yùn)行的,是與內(nèi)核緊密相關(guān)的。從一個(gè)進(jìn)程的運(yùn)行轉(zhuǎn)到另一個(gè)進(jìn)程上運(yùn)行,這個(gè)過(guò)程中經(jīng)過(guò)下面這些變化:
1>保存處理機(jī)上下文,包括程序計(jì)數(shù)器和其他寄存器。
2>更新PCB信息。
3>把進(jìn)程的PCB移入相應(yīng)的隊(duì)列,如就緒、在某事件阻塞等隊(duì)列。
4>選擇另一個(gè)進(jìn)程執(zhí)行,并更新其PCB。
5>更新內(nèi)存管理的數(shù)據(jù)結(jié)構(gòu)。
6>恢復(fù)處理機(jī)上下文。
注:總而言之就是很耗資源,具體的可以參考這篇文章:進(jìn)程切換。
1.3進(jìn)程的阻塞
正在執(zhí)行的進(jìn)程,由于期待的某些事件未發(fā)生,如請(qǐng)求系統(tǒng)資源失敗、等待某種操作的完成、新數(shù)據(jù)尚未到達(dá)或無(wú)新工作做等,則由系統(tǒng)自動(dòng)執(zhí)行阻塞原語(yǔ)(Block),使自己由運(yùn)行狀態(tài)變?yōu)樽枞麪顟B(tài)。可見(jiàn),進(jìn)程的阻塞是進(jìn)程自身的一種主動(dòng)行為,也因此只有處于運(yùn)行態(tài)的進(jìn)程(獲得CPU),才可能將其轉(zhuǎn)為阻塞狀態(tài)。當(dāng)進(jìn)程進(jìn)入阻塞狀態(tài),是不占用CPU資源的。
1.4文件描述符fd
文件描述符(File descriptor)是計(jì)算機(jī)科學(xué)中的一個(gè)術(shù)語(yǔ),是一個(gè)用于表述指向文件的引用的抽象化概念。
文件描述符在形式上是一個(gè)非負(fù)整數(shù)。實(shí)際上,它是一個(gè)索引值,指向內(nèi)核為每一個(gè)進(jìn)程所維護(hù)的該進(jìn)程打開(kāi)文件的記錄表。當(dāng)程序打開(kāi)一個(gè)現(xiàn)有文件或者創(chuàng)建一個(gè)新文件時(shí),內(nèi)核向進(jìn)程返回一個(gè)文件描述符。在程序設(shè)計(jì)中,一些涉及底層的程序編寫(xiě)往往會(huì)圍繞著文件描述符展開(kāi)。但是文件描述符這一概念往往只適用于UNIX、Linux這樣的操作系統(tǒng)。
1.5緩存IO
緩存IO又被稱作標(biāo)準(zhǔn)IO,大多數(shù)文件系統(tǒng)的默認(rèn)IO操作都是緩存IO。在Linux的緩存IO機(jī)制中,操作系統(tǒng)會(huì)將IO的數(shù)據(jù)緩存在文件系統(tǒng)的頁(yè)緩存(page cache)中,也就是說(shuō),數(shù)據(jù)會(huì)先被拷貝到操作系統(tǒng)內(nèi)核的緩沖區(qū)中,然后才會(huì)從操作系統(tǒng)內(nèi)核的緩沖區(qū)拷貝到應(yīng)用程序的地址空間。
緩存IO的缺點(diǎn):
數(shù)據(jù)在傳輸過(guò)程中需要在應(yīng)用程序地址空間和內(nèi)核進(jìn)行多次數(shù)據(jù)拷貝操作,這些數(shù)據(jù)拷貝操作所帶來(lái)的CPU以及內(nèi)存開(kāi)銷是非常大的。
2 ?Linux IO模型
網(wǎng)絡(luò)IO的本質(zhì)是socket的讀取,socket在linux系統(tǒng)被抽象為流,IO可以理解為對(duì)流的操作。剛才說(shuō)了,對(duì)于一次IO訪問(wèn)(以read舉例),數(shù)據(jù)會(huì)先被拷貝到操作系統(tǒng)內(nèi)核的緩沖區(qū)中,然后才會(huì)從操作系統(tǒng)內(nèi)核的緩沖區(qū)拷貝到應(yīng)用程序的地址空間。所以說(shuō),當(dāng)一個(gè)read操作發(fā)生時(shí),它會(huì)經(jīng)歷兩個(gè)階段:
第一階段:等待數(shù)據(jù)準(zhǔn)備(Waiting for the data to be ready)。
第二階段:將數(shù)據(jù)從內(nèi)核拷貝到進(jìn)程中(Copying the data from the kernel to the process)。
對(duì)于socket流而言,
第一步:通常涉及等待網(wǎng)絡(luò)上的數(shù)據(jù)分組到達(dá),然后被復(fù)制到內(nèi)核的某個(gè)緩沖區(qū)。
第二步:把數(shù)據(jù)從內(nèi)核緩沖區(qū)復(fù)制到應(yīng)用進(jìn)程緩沖區(qū)。
網(wǎng)絡(luò)應(yīng)用需要處理的無(wú)非就是兩大類問(wèn)題,網(wǎng)絡(luò)IO,數(shù)據(jù)計(jì)算。相對(duì)于后者,網(wǎng)絡(luò)IO的延遲,給應(yīng)用帶來(lái)的性能瓶頸大于后者。網(wǎng)絡(luò)IO的模型大致有如下幾種:
同步模型(synchronous IO)
? ? 阻塞IO(bloking IO)
? ? 非阻塞IO(non-blocking IO)
? ? 多路復(fù)用IO(multiplexing IO)
? ? 信號(hào)驅(qū)動(dòng)式IO(signal-driven IO)?
異步IO(asynchronous IO)
注:由于signal
driven IO在實(shí)際中并不常用,所以我這只提及剩下的四種IO ?Model。
在深入介紹Linux ?IO各種模型之前,讓我們先來(lái)探索一下基本Linux ?IO模型的簡(jiǎn)單矩陣。如下圖所示:
每個(gè)IO模型都有自己的使用模式,它們對(duì)于特定的應(yīng)用程序都有自己的優(yōu)點(diǎn)。本節(jié)將簡(jiǎn)要對(duì)其一一進(jìn)行介紹。常見(jiàn)的IO模型有阻塞、非阻塞、IO多路復(fù)用,異步。以一個(gè)生動(dòng)形象的例子來(lái)說(shuō)明這四個(gè)概念。周末我和女友去逛街,中午餓了,我們準(zhǔn)備去吃飯。周末人多,吃飯需要排隊(duì),我和女友有以下幾種方案。
2.1同步阻塞IO(blocking IO)
2.1.1場(chǎng)景描述
我和女友點(diǎn)完餐后,不知道什么時(shí)候能做好,只好坐在餐廳里面等,直到做好,然后吃完才離開(kāi)。女友本想還和我一起逛街的,但是不知道飯能什么時(shí)候做好,只好和我一起在餐廳等,而不能去逛街,直到吃完飯才能去逛街,中間等待做飯的時(shí)間浪費(fèi)掉了。這就是典型的阻塞。
2.1.2網(wǎng)絡(luò)模型
同步阻塞IO模型是最常用的一個(gè)模型,也是最簡(jiǎn)單的模型。在linux中,默認(rèn)情況下所有的socket都是blocking。它符合人們最常見(jiàn)的思考邏輯。阻塞就是進(jìn)程"被"休息, ?CPU處理其它進(jìn)程去了。
在這個(gè)IO模型中,用戶空間的應(yīng)用程序執(zhí)行一個(gè)系統(tǒng)調(diào)用(recvform),這會(huì)導(dǎo)致應(yīng)用程序阻塞,什么也不干,直到數(shù)據(jù)準(zhǔn)備好,并且將數(shù)據(jù)從內(nèi)核復(fù)制到用戶進(jìn)程,最后進(jìn)程再處理數(shù)據(jù),在等待數(shù)據(jù)到處理數(shù)據(jù)的兩個(gè)階段,整個(gè)進(jìn)程都被阻塞。不能處理別的網(wǎng)絡(luò)IO。調(diào)用應(yīng)用程序處于一種不再消費(fèi)CPU而只是簡(jiǎn)單等待響應(yīng)的狀態(tài),因此從處理的角度來(lái)看,這是非常有效的。在調(diào)用recv()/recvfrom()函數(shù)時(shí),發(fā)生在內(nèi)核中等待數(shù)據(jù)和復(fù)制數(shù)據(jù)的過(guò)程,大致如下圖:
2.1.3流程描述
當(dāng)用戶進(jìn)程調(diào)用了recv()/recvfrom()這個(gè)系統(tǒng)調(diào)用,kernel就開(kāi)始了IO的第一個(gè)階段:準(zhǔn)備數(shù)據(jù)(對(duì)于網(wǎng)絡(luò)IO來(lái)說(shuō),很多時(shí)候數(shù)據(jù)在一開(kāi)始還沒(méi)有到達(dá)。比如,還沒(méi)有收到一個(gè)完整的UDP包。這個(gè)時(shí)候kernel就要等待足夠的數(shù)據(jù)到來(lái))。這個(gè)過(guò)程需要等待,也就是說(shuō)數(shù)據(jù)被拷貝到操作系統(tǒng)內(nèi)核的緩沖區(qū)中是需要一個(gè)過(guò)程的。而在用戶進(jìn)程這邊,整個(gè)進(jìn)程會(huì)被阻塞(當(dāng)然,是進(jìn)程自己選擇的阻塞)。第二個(gè)階段:當(dāng)kernel一直等到數(shù)據(jù)準(zhǔn)備好了,它就會(huì)將數(shù)據(jù)從kernel中拷貝到用戶內(nèi)存,然后kernel返回結(jié)果,用戶進(jìn)程才解除block的狀態(tài),重新運(yùn)行起來(lái)。所以,blocking IO的特點(diǎn)就是在IO執(zhí)行的兩個(gè)階段都被block了。
優(yōu)點(diǎn):
? ? 能夠及時(shí)返回?cái)?shù)據(jù),無(wú)延遲;
? ? 對(duì)內(nèi)核開(kāi)發(fā)者來(lái)說(shuō)這是省事了;
缺點(diǎn):
? ?對(duì)用戶來(lái)說(shuō)處于等待就要付出性能的代價(jià)了;
2.2同步非阻塞IO(nonblocking ?IO)
2.2.1場(chǎng)景描述
我女友不甘心白白在這等,又想去逛商場(chǎng),又擔(dān)心飯好了。所以我們逛一會(huì),回來(lái)詢問(wèn)服務(wù)員飯好了沒(méi)有,來(lái)來(lái)回回好多次,飯都還沒(méi)吃都快累死了啦。這就是非阻塞。需要不斷的詢問(wèn),是否準(zhǔn)備好了。
2.2.2網(wǎng)絡(luò)模型
同步非阻塞就是 “每隔一會(huì)兒瞄一眼進(jìn)度條”的輪詢(polling)方式。在這種模型中,設(shè)備是以非阻塞的形式打開(kāi)的。這意味著IO操作不會(huì)立即完成,read操作可能會(huì)返回一個(gè)錯(cuò)誤代碼,說(shuō)明這個(gè)命令不能立即滿足(EAGAIN或EWOULDBLOCK)。
在網(wǎng)絡(luò)IO時(shí)候,非阻塞IO也會(huì)進(jìn)行recvform系統(tǒng)調(diào)用,檢查數(shù)據(jù)是否準(zhǔn)備好,與阻塞IO不一樣,"非阻塞將大的整片時(shí)間的阻塞分成N多的小的阻塞,所以進(jìn)程不斷地有機(jī)會(huì)'被' CPU光顧"。
也就是說(shuō)非阻塞的recvform系統(tǒng)調(diào)用調(diào)用之后,進(jìn)程并沒(méi)有被阻塞,內(nèi)核馬上返回給進(jìn)程,如果數(shù)據(jù)還沒(méi)準(zhǔn)備好,此時(shí)會(huì)返回一個(gè)error。進(jìn)程在返回之后,可以干點(diǎn)別的事情,然后再發(fā)起recvform系統(tǒng)調(diào)用。重復(fù)上面的過(guò)程,循環(huán)往復(fù)的進(jìn)行recvform系統(tǒng)調(diào)用。這個(gè)過(guò)程通常被稱之為輪詢。輪詢檢查內(nèi)核數(shù)據(jù),直到數(shù)據(jù)準(zhǔn)備好,再拷貝數(shù)據(jù)到進(jìn)程,進(jìn)行數(shù)據(jù)處理。需要注意,拷貝數(shù)據(jù)整個(gè)過(guò)程,進(jìn)程仍然是屬于阻塞的狀態(tài)。
在linux下,可以通過(guò)設(shè)置socket使其變?yōu)閚on-blocking。當(dāng)對(duì)一個(gè)non-blocking
socket執(zhí)行讀操作時(shí),流程如圖所示:
2.2.3流程描述
當(dāng)用戶進(jìn)程發(fā)出read操作時(shí),如果kernel中的數(shù)據(jù)還沒(méi)有準(zhǔn)備好,那么它并不會(huì)block用戶進(jìn)程,而是立刻返回一個(gè)error。從用戶進(jìn)程角度講,它發(fā)起一個(gè)read操作后,并不需要等待,而是馬上就得到了一個(gè)結(jié)果。用戶進(jìn)程判斷結(jié)果是一個(gè)error時(shí),它就知道數(shù)據(jù)還沒(méi)有準(zhǔn)備好,于是它可以再次發(fā)送read操作。一旦kernel中的數(shù)據(jù)準(zhǔn)備好了,并且又再次收到了用戶進(jìn)程的system call,那么它馬上就將數(shù)據(jù)拷貝到了用戶內(nèi)存,然后返回。所以,nonblocking ?IO的特點(diǎn)是用戶進(jìn)程需要不斷的主動(dòng)詢問(wèn)kernel數(shù)據(jù)好了沒(méi)有。
同步非阻塞方式相比同步阻塞方式:
優(yōu)點(diǎn):能夠在等待任務(wù)完成的時(shí)間里干其他活了(包括提交其他任務(wù),也就是“后臺(tái)” 可以有多個(gè)任務(wù)在同時(shí)執(zhí)行)。
缺點(diǎn):任務(wù)完成的響應(yīng)延遲增大了,因?yàn)槊窟^(guò)一段時(shí)間才去輪詢一次read操作,而任務(wù)可能在兩次輪詢之間的任意時(shí)間完成。這會(huì)導(dǎo)致整體數(shù)據(jù)吞吐量的降低。
(ps:為什么這里還是叫做同步呢?因?yàn)闊o(wú)論是read函數(shù)被設(shè)置成阻塞(默認(rèn))還是非阻塞(O_NONBLOCK),調(diào)用read函數(shù)的進(jìn)程始終處于等待狀態(tài)(等待read返回?cái)?shù)據(jù)給此進(jìn)程):阻塞的時(shí)候read一直等待數(shù)據(jù)返回,非阻塞的時(shí)候read一直等待正確數(shù)據(jù)返回.)
2.3 IO多路復(fù)用(IO ?multiplexing)
2.3.1場(chǎng)景描述
與第二個(gè)方案差不多,餐廳安裝了電子屏幕用來(lái)顯示點(diǎn)餐的狀態(tài),這樣我和女友逛街一會(huì),回來(lái)就不用去詢問(wèn)服務(wù)員了,直接看電子屏幕就可以了。這樣每個(gè)人的餐是否好了,都直接看電子屏幕就可以了,這就是典型的IO多路復(fù)用。
(PS:多了個(gè)電子屏幕)
2.3.2網(wǎng)絡(luò)模型
由于同步非阻塞方式需要不斷主動(dòng)輪詢,輪詢占據(jù)了很大一部分過(guò)程,輪詢會(huì)消耗大量的CPU時(shí)間,而 “后臺(tái)” 可能有多個(gè)任務(wù)在同時(shí)進(jìn)行,人們就想到了循環(huán)查詢多個(gè)任務(wù)的完成狀態(tài),只要有任何一個(gè)任務(wù)完成,就去處理它。如果輪詢不是進(jìn)程的用戶態(tài)(PS:??),而是有人幫忙就好了。那么這就是所謂的 “IO多路復(fù)用”。UNIX/Linux下的select、poll、epoll就是干這個(gè)的(epoll比poll、select效率高,做的事情是一樣的)。
IO多路復(fù)用有兩個(gè)特別的系統(tǒng)調(diào)用select、poll、epoll函數(shù)。select調(diào)用是內(nèi)核級(jí)別的,select輪詢相對(duì)非阻塞的輪詢的區(qū)別在于---前者可以等待多個(gè)socket,能實(shí)現(xiàn)同時(shí)對(duì)多個(gè)IO端口進(jìn)行監(jiān)聽(tīng),當(dāng)其中任何一個(gè)socket的數(shù)據(jù)準(zhǔn)好了,就能返回進(jìn)行可讀,然后進(jìn)程再進(jìn)行recvform系統(tǒng)調(diào)用,將數(shù)據(jù)由內(nèi)核拷貝到用戶進(jìn)程,當(dāng)然這個(gè)過(guò)程是阻塞的。select或poll調(diào)用之后,會(huì)阻塞進(jìn)程,與blocking ?IO阻塞不同在于,此時(shí)的select不是等到socket數(shù)據(jù)全部到達(dá)再處理,而是有了一部分?jǐn)?shù)據(jù)就會(huì)調(diào)用用戶進(jìn)程來(lái)處理。如何知道有一部分?jǐn)?shù)據(jù)到達(dá)了呢?監(jiān)視的事情交給了內(nèi)核,內(nèi)核負(fù)責(zé)數(shù)據(jù)到達(dá)的處理。也可以理解為"非阻塞"吧。
I/O復(fù)用模型會(huì)用到select、poll、epoll函數(shù),這幾個(gè)函數(shù)也會(huì)使進(jìn)程阻塞,但是和阻塞I/O所不同的的,這兩個(gè)函數(shù)可以同時(shí)阻塞多個(gè)I/O操作。而且可以同時(shí)對(duì)多個(gè)讀操作,多個(gè)寫(xiě)操作的I/O函數(shù)進(jìn)行檢測(cè),直到有數(shù)據(jù)可讀或可寫(xiě)時(shí)(注意不是全部數(shù)據(jù)可讀或可寫(xiě)),才真正調(diào)用I/O操作函數(shù)。
對(duì)于多路復(fù)用,也就是輪詢多個(gè)socket。多路復(fù)用既然可以處理多個(gè)IO,也就帶來(lái)了新的問(wèn)題,多個(gè)IO之間的順序變得不確定了,當(dāng)然也可以針對(duì)不同的編號(hào)。具體流程,如下圖所示:
2.3.3流程描述
IO multiplexing就是我們說(shuō)的select,poll,epoll,有些地方也稱這種IO方式為event ?driven IO。select/epoll的好處就在于單個(gè)process就可以同時(shí)處理多個(gè)網(wǎng)絡(luò)連接的IO。它的基本原理就是select,poll,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)程。
多路復(fù)用的特點(diǎn)是通過(guò)一種機(jī)制一個(gè)進(jìn)程能同時(shí)等待IO文件描述符,內(nèi)核監(jiān)視這些文件描述符(套接字描述符),其中的任意一個(gè)進(jìn)入讀就緒狀態(tài),select,poll,epoll函數(shù)就可以返回。對(duì)于監(jiān)視的方式,又可以分為select,poll,epoll三種方式。
上面的圖和blocking IO的圖其實(shí)并沒(méi)有太大的不同,事實(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的。只不過(guò)process是被select這個(gè)函數(shù)block,而不是被socket IO給block。所以IO多路復(fù)用是阻塞在select,epoll這樣的系統(tǒng)調(diào)用之上,而沒(méi)有阻塞在真正的I/O系統(tǒng)調(diào)用如recvfrom之上。
在I/O編程過(guò)程中,當(dāng)需要同時(shí)處理多個(gè)客戶端接入請(qǐng)求時(shí),可以利用多線程或者I/O多路復(fù)用技術(shù)進(jìn)行處理。I/O多路復(fù)用技術(shù)通過(guò)把多個(gè)I/O的阻塞復(fù)用到同一個(gè)select的阻塞上,從而使得系統(tǒng)在單線程的情況下可以同時(shí)處理多個(gè)客戶端請(qǐng)求。與傳統(tǒng)的多線程/多進(jìn)程模型比,I/O多路復(fù)用的最大優(yōu)勢(shì)是系統(tǒng)開(kāi)銷小,系統(tǒng)不需要?jiǎng)?chuàng)建新的額外進(jìn)程或者線程,也不需要維護(hù)這些進(jìn)程和線程的運(yùn)行,降底了系統(tǒng)的維護(hù)工作量,節(jié)省了系統(tǒng)資源,I/O多路復(fù)用的主要應(yīng)用場(chǎng)景如下:
服務(wù)器需要同時(shí)處理多個(gè)處于監(jiān)聽(tīng)狀態(tài)或者多個(gè)連接狀態(tài)的套接字。
服務(wù)器需要同時(shí)處理多種網(wǎng)絡(luò)協(xié)議的套接字。
了解了前面三種IO模式,在用戶進(jìn)程進(jìn)行系統(tǒng)調(diào)用的時(shí)候,他們?cè)诘却龜?shù)據(jù)到來(lái)的時(shí)候,處理的方式不一樣,直接等待,輪詢,select或poll輪詢,兩個(gè)階段過(guò)程:
第一個(gè)階段有的阻塞,有的不阻塞,有的可以阻塞又可以不阻塞。
第二個(gè)階段都是阻塞的。
從整個(gè)IO過(guò)程來(lái)看,他們都是順序執(zhí)行的,因此可以歸為同步模型(synchronous)。都是進(jìn)程主動(dòng)等待且向內(nèi)核檢查狀態(tài)。【此句很重要!!!】
高并發(fā)的程序一般使用同步非阻塞方式而非多線程+同步阻塞方式。要理解這一點(diǎn),首先要扯到并發(fā)和并行的區(qū)別。比如去某部門辦事需要依次去幾個(gè)窗口,辦事大廳里的人數(shù)就是并發(fā)數(shù),而窗口個(gè)數(shù)就是并行度。也就是說(shuō)并發(fā)數(shù)是指同時(shí)進(jìn)行的任務(wù)數(shù)(如同時(shí)服務(wù)的HTTP請(qǐng)求),而并行數(shù)是可以同時(shí)工作的物理資源數(shù)量(如CPU核數(shù))。通過(guò)合理調(diào)度任務(wù)的不同階段,并發(fā)數(shù)可以遠(yuǎn)遠(yuǎn)大于并行度,這就是區(qū)區(qū)幾個(gè)CPU可以支持上萬(wàn)個(gè)用戶并發(fā)請(qǐng)求的奧秘。在這種高并發(fā)的情況下,為每個(gè)任務(wù)(用戶請(qǐng)求)創(chuàng)建一個(gè)進(jìn)程或線程的開(kāi)銷非常大。而同步非阻塞方式可以把多個(gè)IO請(qǐng)求丟到后臺(tái)去,這就可以在一個(gè)進(jìn)程里服務(wù)大量的并發(fā)IO請(qǐng)求。
注意:IO多路復(fù)用是同步阻塞模型還是異步阻塞模型,在此給大家分析下:
此處仍然不太清楚的,強(qiáng)烈建議大家在細(xì)究《聊聊同步、異步、阻塞與非阻塞》中講同步與異步的根本性區(qū)別,同步是需要主動(dòng)等待消息通知,而異步則是被動(dòng)接收消息通知,通過(guò)回調(diào)、通知、狀態(tài)等方式來(lái)被動(dòng)獲取消息。IO多路復(fù)用在阻塞到select階段時(shí),用戶進(jìn)程是主動(dòng)等待并調(diào)用select函數(shù)獲取數(shù)據(jù)就緒狀態(tài)消息,并且其進(jìn)程狀態(tài)為阻塞。所以,把IO多路復(fù)用歸為同步阻塞模式。
2.4信號(hào)驅(qū)動(dòng)式IO(signal-driven ? IO)
信號(hào)驅(qū)動(dòng)式I/O:首先我們?cè)试SSocket進(jìn)行信號(hào)驅(qū)動(dòng)IO,并安裝一個(gè)信號(hào)處理函數(shù),進(jìn)程繼續(xù)運(yùn)行并不阻塞。當(dāng)數(shù)據(jù)準(zhǔn)備好時(shí),進(jìn)程會(huì)收到一個(gè)SIGIO信號(hào),可以在信號(hào)處理函數(shù)中調(diào)用I/O操作函數(shù)處理數(shù)據(jù)。過(guò)程如下圖所示:
2.5異步非阻塞IO(asynchronous ? IO)
2.5.1場(chǎng)景描述
女友不想逛街,又餐廳太吵了,回家好好休息一下。于是我們叫外賣,打個(gè)電話點(diǎn)餐,然后我和女友可以在家好好休息一下,飯好了送貨員送到家里來(lái)。這就是典型的異步,只需要打個(gè)電話說(shuō)一下,然后可以做自己的事情,飯好了就送來(lái)了。
2.5.2網(wǎng)絡(luò)模型
相對(duì)于同步IO,異步IO不是順序執(zhí)行。用戶進(jìn)程進(jìn)行aio_read系統(tǒng)調(diào)用之后,無(wú)論內(nèi)核數(shù)據(jù)是否準(zhǔn)備好,都會(huì)直接返回給用戶進(jìn)程,然后用戶態(tài)進(jìn)程可以去做別的事情。等到socket數(shù)據(jù)準(zhǔn)備好了,內(nèi)核直接復(fù)制數(shù)據(jù)給進(jìn)程,然后從內(nèi)核向進(jìn)程發(fā)送通知。IO兩個(gè)階段,進(jìn)程都是非阻塞的。
Linux提供了AIO庫(kù)函數(shù)實(shí)現(xiàn)異步,但是用的很少。目前有很多開(kāi)源的異步IO庫(kù),例如libevent、libev、libuv。異步過(guò)程如下圖所示:
2.5.3流程描述
用戶進(jìn)程發(fā)起aio_read操作之后,立刻就可以開(kāi)始去做其它的事。而另一方面,從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或執(zhí)行一個(gè)基于線程的回調(diào)函數(shù)來(lái)完成這次IO處理過(guò)程,告訴它read操作完成了。
在Linux中,通知的方式是 ?“信號(hào)”:
如果這個(gè)進(jìn)程正在用戶態(tài)忙著做別的事(例如在計(jì)算兩個(gè)矩陣的乘積),那就強(qiáng)行打斷之,調(diào)用事先注冊(cè)的信號(hào)處理函數(shù),這個(gè)函數(shù)可以決定何時(shí)以及如何處理這個(gè)異步任務(wù)。由于信號(hào)處理函數(shù)是突然闖進(jìn)來(lái)的,因此跟中斷處理程序一樣,有很多事情是不能做的,因此保險(xiǎn)起見(jiàn),一般是把事件“登記” 一下放進(jìn)隊(duì)列,然后返回該進(jìn)程原來(lái)在做的事。如果這個(gè)進(jìn)程正在內(nèi)核態(tài)忙著做別的事,例如以同步阻塞方式讀寫(xiě)磁盤,那就只好把這個(gè)通知掛起來(lái)了,等到內(nèi)核態(tài)的事情忙完了,快要回到用戶態(tài)的時(shí)候,再觸發(fā)信號(hào)通知。
如果這個(gè)進(jìn)程現(xiàn)在被掛起了,例如無(wú)事可做sleep了,那就把這個(gè)進(jìn)程喚醒,下次有CPU空閑的時(shí)候,就會(huì)調(diào)度到這個(gè)進(jìn)程,觸發(fā)信號(hào)通知。
異步API說(shuō)來(lái)輕巧,做來(lái)難,這主要是對(duì)API的實(shí)現(xiàn)者而言的。Linux的異步IO(AIO)支持是2.6.22才引入的,還有很多系統(tǒng)調(diào)用不支持異步IO。Linux的異步IO最初是為數(shù)據(jù)庫(kù)設(shè)計(jì)的,因此通過(guò)異步IO的讀寫(xiě)操作不會(huì)被緩存或緩沖,這就無(wú)法利用操作系統(tǒng)的緩存與緩沖機(jī)制。
很多人把Linux的O_NONBLOCK認(rèn)為是異步方式,但事實(shí)上這是前面講的同步非阻塞方式。需要指出的是,雖然Linux上的IO
API略顯粗糙,但每種編程框架都有封裝好的異步IO實(shí)現(xiàn)。操作系統(tǒng)少做事,把更多的自由留給用戶,正是UNIX的設(shè)計(jì)哲學(xué),也是Linux上編程框架百花齊放的一個(gè)原因。
從前面IO模型的分類中,我們可以看出AIO的動(dòng)機(jī):
同步阻塞模型需要在IO操作開(kāi)始時(shí)阻塞應(yīng)用程序。這意味著不可能同時(shí)重疊進(jìn)行處理和IO操作。
同步非阻塞模型允許處理和IO操作重疊進(jìn)行,但是這需要應(yīng)用程序根據(jù)重現(xiàn)的規(guī)則來(lái)檢查IO操作的狀態(tài)。
這樣就剩下異步非阻塞IO了,它允許處理和IO操作重疊進(jìn)行,包括IO操作完成的通知。
IO多路復(fù)用除了需要阻塞之外,select函數(shù)所提供的功能(異步阻塞IO)與AIO類似。不過(guò),它是對(duì)通知事件進(jìn)行阻塞,而不是對(duì)IO調(diào)用進(jìn)行阻塞。
2.6關(guān)于異步阻塞
有時(shí)我們的API只提供異步通知方式,例如在node.js里,但業(yè)務(wù)邏輯需要的是做完一件事后做另一件事,例如數(shù)據(jù)庫(kù)連接初始化后才能開(kāi)始接受用戶的HTTP請(qǐng)求。這樣的業(yè)務(wù)邏輯就需要調(diào)用者是以阻塞方式來(lái)工作。
為了在異步環(huán)境里模擬 “順序執(zhí)行” ?的效果,就需要把同步代碼轉(zhuǎn)換成異步形式,這稱為CPS(Continuation Passing Style)變換。BYVoid大神的continuation.js庫(kù)就是一個(gè)CPS變換的工具。用戶只需用比較符合人類常理的同步方式書(shū)寫(xiě)代碼,CPS變換器會(huì)把它轉(zhuǎn)換成層層嵌套的異步回調(diào)形式。
另外一種使用阻塞方式的理由是降低響應(yīng)延遲。如果采用非阻塞方式,一個(gè)任務(wù)A被提交到后臺(tái),就開(kāi)始做另一件事B,但B還沒(méi)做完,A就完成了,這時(shí)要想讓A的完成事件被盡快處理(比如A是個(gè)緊急事務(wù)),要么丟棄做到一半的B,要么保存B的中間狀態(tài)并切換回A,任務(wù)的切換是需要時(shí)間的(不管是從磁盤載入到內(nèi)存,還是從內(nèi)存載入到高速緩存),這勢(shì)必降低A的響應(yīng)速度。因此,對(duì)實(shí)時(shí)系統(tǒng)或者延遲敏感的事務(wù),有時(shí)采用阻塞方式比非阻塞方式更好。
3五種IO模型總結(jié)
3.1 blocking和non-blocking區(qū)別
調(diào)用blocking IO會(huì)一直block住對(duì)應(yīng)的進(jìn)程直到操作完成,而non-blocking IO在kernel還準(zhǔn)備數(shù)據(jù)的情況下會(huì)立刻返回。
3.2 synchronous IO和asynchronous IO區(qū)別
在說(shuō)明synchronous IO和asynchronous IO的區(qū)別之前,需要先給出兩者的定義。POSIX的定義是這樣子的:
A synchronous I/O operation causes the requesting process to be blocked until that I/O operation completes;
An asynchronous I/O operation does not cause the requesting process to be blocked;
兩者的區(qū)別就在于synchronous
IO做”IO operation”的時(shí)候會(huì)將process阻塞。按照這個(gè)定義,之前所述的blocking IO,non-blocking IO,IO multiplexing都屬于synchronous ?IO。
有人會(huì)說(shuō),non-blocking IO并沒(méi)有被block啊。這里有個(gè)非常“狡猾”的地方,定義中所指的”IO operation”是指真實(shí)的IO操作,就是例子中的recvfrom這個(gè)system call。non-blocking IO在執(zhí)行recvfrom這個(gè)system call的時(shí)候,如果kernel的數(shù)據(jù)沒(méi)有準(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的。而asynchronousIO則不一樣,當(dāng)進(jìn)程發(fā)起IO操作之后,就直接返回再也不理睬了,直到kernel發(fā)送一個(gè)信號(hào),告訴進(jìn)程說(shuō)IO完成。在這整個(gè)過(guò)程中,進(jìn)程完全沒(méi)有被block。各個(gè)IO
Model的比較如圖所示:
通過(guò)上面的圖片,可以發(fā)現(xiàn)non-blocking IO和asynchronous IO的區(qū)別還是很明顯的。在non-blocking IO中,雖然進(jìn)程大部分時(shí)間都不會(huì)被block,但是它仍然要求進(jìn)程去主動(dòng)的check,并且當(dāng)數(shù)據(jù)準(zhǔn)備完成以后,也需要進(jìn)程主動(dòng)的再次調(diào)用recvfrom來(lái)將數(shù)據(jù)拷貝到用戶內(nèi)存。而asynchronous IO則完全不同。它就像是用戶進(jìn)程將整個(gè)IO操作交給了他人(kernel)完成,然后他人做完后發(fā)信號(hào)通知。在此期間,用戶進(jìn)程不需要去檢查IO操作的狀態(tài),也不需要主動(dòng)的去拷貝數(shù)據(jù)。