轉(zhuǎn)自: http://www.reibang.com/p/486b0965c296? ? http://www.reibang.com/p/486b0965c296
轉(zhuǎn)自 簡(jiǎn)書作者?陶邦仁?的文章,并讀后有感:感覺作者上面鏈接的兩篇文章放到一處更好理解一些,所以做了復(fù)制.自覺后面異步的部分應(yīng)該把異步的現(xiàn)狀說一下,以便讓人有充分的了解. 文中加了一些PS,是我自己的理解,如有誤導(dǎo),請(qǐng)忽略之......
聊聊同步窖张、異步充活、阻塞與非阻塞
近來遇到了一些常見的概念,尤其是網(wǎng)絡(luò)編程方面的概念辛萍,如:阻塞阻塑、非阻塞、異步I/O等等,對(duì)于這些概念自己也沒有太清晰的認(rèn)識(shí),只是很模糊的概念盼樟,說了解吧也了解,但是要讓自己準(zhǔn)確的描述概念方面的具體細(xì)節(jié)沟涨,卻說的不那么準(zhǔn)確恤批,這也是自己在這幾個(gè)方面也沒有細(xì)細(xì)考究過的原因吧异吻。經(jīng)過看了些這幾個(gè)概念的資料裹赴,發(fā)現(xiàn)同步、異步诀浪、阻塞棋返、非阻塞的概念其實(shí)也并不難以理解,在此寫下此文雷猪,歡迎拍磚睛竣,希望多多交流。
1同步與異步
首先來解釋同步和異步的概念求摇,這兩個(gè)概念與消息的通知機(jī)制有關(guān)射沟。也就是同步與異步主要是從消息通知機(jī)制角度來說的。
1.1概念描述
所謂同步就是一個(gè)任務(wù)的完成需要依賴另外一個(gè)任務(wù)時(shí)与境,只有等待被依賴的任務(wù)完成后验夯,依賴的任務(wù)才能算完成,這是一種可靠的任務(wù)序列摔刁。要么成功都成功挥转,失敗都失敗,兩個(gè)任務(wù)的狀態(tài)可以保持一致。所謂異步是不需要等待被依賴的任務(wù)完成绑谣,只是通知被依賴的任務(wù)要完成什么工作党窜,依賴的任務(wù)也立即執(zhí)行,只要自己完成了整個(gè)任務(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è)異步過程調(diào)用發(fā)出后,調(diào)用者不能立刻得到返回消息(結(jié)果)括改。實(shí)際處理這個(gè)調(diào)用的部件在完成后腻豌,通過狀態(tài)、通知和回調(diào)來通知調(diào)用者嘱能。這里提到執(zhí)行部件和調(diào)用者通過三種途徑返回結(jié)果:狀態(tài)吝梅、通知和回調(diào)。使用哪一種通知機(jī)制惹骂,依賴于執(zhí)行部件的實(shí)現(xiàn)苏携,除非執(zhí)行部件提供多種選擇,否則不受調(diào)用者控制对粪。
如果執(zhí)行部件用狀態(tài)來通知右冻,那么調(diào)用者就需要每隔一定時(shí)間檢查一次,效率就很低(有些初學(xué)多線程編程的人著拭,總喜歡用一個(gè)循環(huán)去檢查某個(gè)變量的值纱扭,這其實(shí)是一種很嚴(yán)重的錯(cuò)誤);
如果是使用通知的方式儡遮,效率則很高乳蛾,因?yàn)閳?zhí)行部件幾乎不需要做額外的操作。至于回調(diào)函數(shù)鄙币,其實(shí)和通知沒太多區(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)的人)通過某種機(jī)制(在這里是寫在小紙條上的號(hào)碼,喊號(hào))找到等待該事件的人剥险。阻塞非阻塞指的是一個(gè)主體,同步非同步指的是兩個(gè)以上的主體.
2阻塞與非阻塞
阻塞和非阻塞這兩個(gè)概念與程序(線程)等待消息通知(無所謂同步或者異步)時(shí)的狀態(tài)有關(guān)聪蘸。也就是說阻塞與非阻塞主要是程序(線程)等待消息通知時(shí)的狀態(tài)角度來說的。
2.1概念描述
阻塞調(diào)用是指調(diào)用結(jié)果返回之前表制,當(dāng)前線程會(huì)被掛起健爬,一直處于等待消息通知,不能夠執(zhí)行其他業(yè)務(wù)么介。函數(shù)只有在得到結(jié)果之后才會(huì)返回娜遵。有人也許會(huì)把阻塞調(diào)用和同步調(diào)用等同起來,實(shí)際上它們是不同的壤短。
對(duì)于同步調(diào)用來說设拟,很多時(shí)候當(dāng)前線程可能還是激活的,只是從邏輯上當(dāng)前函數(shù)沒有返回而已久脯,此時(shí)纳胧,這個(gè)線程可能也會(huì)處理其他的消息。還有一點(diǎn)帘撰,在這里先擴(kuò)展下:(a)如果這個(gè)線程在等待當(dāng)前函數(shù)返回時(shí)跑慕,仍在執(zhí)行其他消息處理,那這種情況就叫做同步非阻塞摧找;(b)如果這個(gè)線程在等待當(dāng)前函數(shù)返回時(shí)核行,沒有執(zhí)行其他消息處理,而是處于掛起等待狀態(tài)慰于,那這種情況就叫做同步阻塞钮科;所以同步的實(shí)現(xiàn)方式會(huì)有兩種:同步阻塞唤衫、同步非阻塞婆赠;同理,異步也會(huì)有兩種實(shí)現(xiàn):異步阻塞佳励、異步非阻塞休里;
對(duì)于阻塞調(diào)用來說,則當(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è)等待的過程中怕品,等待者除了等待消息通知之外不能做其它的事情妇垢,那么該機(jī)制就是阻塞的,表現(xiàn)在程序中,也就是該程序一直阻塞在該函數(shù)調(diào)用處不能繼續(xù)往下執(zhí)行肉康。相反闯估,有的人喜歡在銀行辦理這些業(yè)務(wù)的時(shí)候一邊打打電話發(fā)發(fā)短信一邊等待,這樣的狀態(tài)就是非阻塞的吼和,因?yàn)樗?等待者)沒有阻塞在這個(gè)消息通知上涨薪,而是一邊做自己的事情一邊等待。但是需要注意了炫乓,同步非阻塞形式實(shí)際上是效率低下的尤辱,想象一下你一邊打著電話一邊還需要抬頭看到底隊(duì)伍排到你了沒有。如果把打電話和觀察排隊(duì)的位置看成是程序的兩個(gè)操作的話厢岂,這個(gè)程序需要在這兩種不同的行為之間來回的切換光督,效率可想而知是低下的;而異步非阻塞形式卻沒有這樣的問題塔粒,因?yàn)榇螂娫捠悄?等待者)的事情结借,而通知你則是柜臺(tái)(消息觸發(fā)機(jī)制)的事情,程序沒有在兩種不同的操作中來回切換卒茬。
3同步/異步與阻塞/非阻塞
同步阻塞形式效率是最低的船老,拿上面的例子來說,就是你專心排隊(duì)圃酵,什么別的事都不做柳畔。實(shí)際程序中:就是未對(duì)fd設(shè)置O_NONBLOCK標(biāo)志位的read/write操作;
異步阻塞形式如果在銀行等待辦理業(yè)務(wù)的人采用的是異步的方式去等待消息被觸發(fā)(通知)郭赐,也就是領(lǐng)了一張小紙條薪韩,假如在這段時(shí)間里他不能離開銀行做其它的事情,那么很顯然捌锭,這個(gè)人被阻塞在了這個(gè)等待的操作上面俘陷;異步操作是可以被阻塞住的,只不過它不是在處理消息時(shí)阻塞观谦,而是在等待消息通知時(shí)被阻塞拉盾。比如select函數(shù),假如傳入的最后一個(gè)timeout參數(shù)為NULL豁状,那么如果所關(guān)注的事件沒有一個(gè)被觸發(fā)捉偏,程序就會(huì)一直阻塞在這個(gè)select調(diào)用處倒得。
同步非阻塞形式實(shí)際上是效率低下的,想象一下你一邊打著電話一邊還需要抬頭看到底隊(duì)伍排到你了沒有夭禽,如果把打電話和觀察排隊(duì)的位置看成是程序的兩個(gè)操作的話屎暇,這個(gè)程序需要在這兩種不同的行為之間來回的切換,效率可想而知是低下的驻粟。很多人會(huì)寫阻塞的read/write操作根悼,但是別忘了可以對(duì)fd設(shè)置O_NONBLOCK標(biāo)志位,這樣就可以將同步操作變成非阻塞的了蜀撑。
異步非阻塞形式效率更高挤巡,因?yàn)榇螂娫捠悄?等待者)的事情,而通知你則是柜臺(tái)(消息觸發(fā)機(jī)制)的事情酷麦,程序沒有在兩種不同的操作中來回切換矿卑。比如說,這個(gè)人突然發(fā)覺自己煙癮犯了沃饶,需要出去抽根煙母廷,于是他告訴大堂經(jīng)理說,排到我這個(gè)號(hào)碼的時(shí)候麻煩到外面通知我一下(注冊(cè)一個(gè)回調(diào)函數(shù))糊肤,那么他就沒有被阻塞在這個(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)出來届宠,比如很多人會(huì)寫阻塞的read/write操作圃泡,但是別忘了可以對(duì)fd設(shè)置O_NONBLOCK標(biāo)志位链方,這樣就可以將同步操作變成非阻塞的了。但最根本是因?yàn)闆]有區(qū)分這兩個(gè)概念镶摘,比如阻塞的read/write操作中盲憎,其實(shí)是把消息通知機(jī)制和等待消息通知的狀態(tài)結(jié)合在了一起辟躏,在這里所關(guān)注的消息就是fd是否可讀/寫君纫,而等待消息通知的狀態(tài)則是對(duì)fd可讀/寫等待過程中程序(線程)的狀態(tài)驯遇。當(dāng)我們將這個(gè)fd設(shè)置為非阻塞的時(shí)候,read/write操作就不會(huì)在等待消息通知這里阻塞蓄髓,如果fd不可讀/寫則操作立即返回。同樣的舒帮,很多人也會(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)明確說明预茄,同步/異步關(guān)注的是消息通知的機(jī)制兴溜,而阻塞/非阻塞關(guān)注的是程序(線程)等待消息通知時(shí)的狀態(tài)。以小明下載文件打個(gè)比方耻陕,從這兩個(gè)關(guān)注點(diǎn)來再次說明這兩組概念拙徽,希望能夠更好的促進(jìn)大家的理解。
同步阻塞:小明一直盯著下載進(jìn)度條诗宣,到100%的時(shí)候就完成膘怕。同步體現(xiàn)在:等待下載完成通知;阻塞體現(xiàn)在:等待下載完成通知過程中召庞,不能做其他任務(wù)處理岛心;
同步非阻塞:小明提交下載任務(wù)后就去干別的,每過一段時(shí)間就去瞄一眼進(jìn)度條篮灼,看到100%就完成忘古。同步體現(xiàn)在:等待下載完成通知;非阻塞體現(xiàn)在:等待下載完成通知過程中诅诱,去干別的任務(wù)了存皂,只是時(shí)不時(shí)會(huì)瞄一眼進(jìn)度條;【小明必須要在兩個(gè)任務(wù)間切換逢艘,關(guān)注下載進(jìn)度】
異步阻塞:小明換了個(gè)有下載完成通知功能的軟件旦袋,下載完成就“叮”一聲它改。不過小明仍然一直等待“栋淘校”的聲音(看起來很傻,不是嗎)央拖。異步體現(xiàn)在:下載完成“都婪В”一聲通知;阻塞體現(xiàn)在:等待下載完成“断式洌”一聲通知過程中专控,不能做其他任務(wù)處理;
異步非阻塞:仍然是那個(gè)會(huì)“抖舨停”一聲的下載軟件伦腐,小明提交下載任務(wù)后就去干別的,聽到“妒Ф迹”的一聲就知道完成了柏蘑。異步體現(xiàn)在:下載完成“缎叶常”一聲通知;非阻塞體現(xiàn)在:等待下載完成“犊确伲”一聲通知過程中洽损,去干別的任務(wù)了,只需要接收“陡锇耄”聲通知即可碑定;【軟件處理下載任務(wù),小明處理其他任務(wù)又官,不需關(guān)注進(jìn)度延刘,只需接收軟件“叮”聲通知赏胚,即可】
也就是說访娶,同步/異步是“下載完成消息”通知的方式(機(jī)制),而阻塞/非阻塞則是在等待“下載完成消息”通知過程中的狀態(tài)(能不能干其他任務(wù))觉阅,在不同的場(chǎng)景下崖疤,同步/異步、阻塞/非阻塞的四種組合都有應(yīng)用典勇。所以劫哼,綜上所述,同步和異步僅僅是關(guān)注的消息如何通知的機(jī)制割笙,而阻塞與非阻塞關(guān)注的是等待消息通知時(shí)的狀態(tài)权烧。也就是說,同步的情況下伤溉,是由處理消息者自己去等待消息是否被觸發(fā)般码,而異步的情況下是由觸發(fā)機(jī)制來通知處理消息者,所以在異步機(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)了博其,沒有看過的套才,建議先看這篇博文理解這兩個(gè)概念點(diǎn)迂猴。在認(rèn)知上,建立統(tǒng)一的模型背伴。這樣沸毁,大家在繼續(xù)看本篇時(shí),才不會(huì)理解有偏差傻寂。那么息尺,在正式開始講Linux IO模型前,比如:同步IO和異步IO疾掰,阻塞IO和非阻塞IO分別是什么搂誉,到底有什么區(qū)別?不同的人在不同的上下文下給出的答案是不同的静檬。所以先限定一下本文的上下文炭懊。
1概念說明
在進(jìn)行解釋之前,首先要說明幾個(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)用程序父阻,可以訪問受保護(hù)的內(nèi)存空間,也有訪問底層硬件設(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)程切換涨颜。因此可以說费韭,任何進(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è)過程中經(jīng)過下面這些變化:
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á)或無新工作做等就乓,則由系統(tǒng)自動(dòng)執(zhí)行阻塞原語(Block)汉匙,使自己由運(yùn)行狀態(tài)變?yōu)樽枞麪顟B(tài)∩希可見噩翠,進(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ù)語屯援,是一個(gè)用于表述指向文件的引用的抽象化概念。
文件描述符在形式上是一個(gè)非負(fù)整數(shù)念脯。實(shí)際上狞洋,它是一個(gè)索引值,指向內(nèi)核為每一個(gè)進(jìn)程所維護(hù)的該進(jìn)程打開文件的記錄表绿店。當(dāng)程序打開一個(gè)現(xiàn)有文件或者創(chuàng)建一個(gè)新文件時(shí)吉懊,內(nèi)核向進(jìn)程返回一個(gè)文件描述符庐橙。在程序設(shè)計(jì)中,一些涉及底層的程序編寫往往會(huì)圍繞著文件描述符展開借嗽。但是文件描述符這一概念往往只適用于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)的頁緩存(page cache)中甲锡,也就是說兆蕉,數(shù)據(jù)會(huì)先被拷貝到操作系統(tǒng)內(nèi)核的緩沖區(qū)中羽戒,然后才會(huì)從操作系統(tǒng)內(nèi)核的緩沖區(qū)拷貝到應(yīng)用程序的地址空間缤沦。
緩存IO的缺點(diǎn):
數(shù)據(jù)在傳輸過程中需要在應(yīng)用程序地址空間和內(nèi)核進(jìn)行多次數(shù)據(jù)拷貝操作,這些數(shù)據(jù)拷貝操作所帶來的CPU以及內(nèi)存開銷是非常大的易稠。
2 ?Linux IO模型
網(wǎng)絡(luò)IO的本質(zhì)是socket的讀取缸废,socket在linux系統(tǒng)被抽象為流,IO可以理解為對(duì)流的操作驶社。剛才說了企量,對(duì)于一次IO訪問(以read舉例),數(shù)據(jù)會(huì)先被拷貝到操作系統(tǒng)內(nèi)核的緩沖區(qū)中亡电,然后才會(huì)從操作系統(tǒng)內(nèi)核的緩沖區(qū)拷貝到應(yīng)用程序的地址空間届巩。所以說,當(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ǎng)絡(luò)IO,數(shù)據(jù)計(jì)算耳鸯。相對(duì)于后者湿蛔,網(wǎng)絡(luò)IO的延遲,給應(yīng)用帶來的性能瓶頸大于后者县爬。網(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各種模型之前捌省,讓我們先來探索一下基本Linux ?IO模型的簡(jiǎn)單矩陣苫纤。如下圖所示:
每個(gè)IO模型都有自己的使用模式,它們對(duì)于特定的應(yīng)用程序都有自己的優(yōu)點(diǎn)。本節(jié)將簡(jiǎn)要對(duì)其一一進(jìn)行介紹卷拘。常見的IO模型有阻塞喊废、非阻塞、IO多路復(fù)用栗弟,異步污筷。以一個(gè)生動(dòng)形象的例子來說明這四個(gè)概念。周末我和女友去逛街乍赫,中午餓了瓣蛀,我們準(zhǔn)備去吃飯。周末人多雷厂,吃飯需要排隊(duì)惋增,我和女友有以下幾種方案。
2.1同步阻塞IO(blocking IO)
2.1.1場(chǎng)景描述
我和女友點(diǎn)完餐后改鲫,不知道什么時(shí)候能做好诈皿,只好坐在餐廳里面等,直到做好像棘,然后吃完才離開稽亏。女友本想還和我一起逛街的,但是不知道飯能什么時(shí)候做好缕题,只好和我一起在餐廳等截歉,而不能去逛街,直到吃完飯才能去逛街烟零,中間等待做飯的時(shí)間浪費(fèi)掉了瘪松。這就是典型的阻塞。
2.1.2網(wǎng)絡(luò)模型
同步阻塞IO模型是最常用的一個(gè)模型瓶摆,也是最簡(jiǎn)單的模型凉逛。在linux中,默認(rèn)情況下所有的socket都是blocking群井。它符合人們最常見的思考邏輯状飞。阻塞就是進(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)悦穿,因此從處理的角度來看攻礼,這是非常有效的。在調(diào)用recv()/recvfrom()函數(shù)時(shí)栗柒,發(fā)生在內(nèi)核中等待數(shù)據(jù)和復(fù)制數(shù)據(jù)的過程礁扮,大致如下圖:
2.1.3流程描述
當(dāng)用戶進(jìn)程調(diào)用了recv()/recvfrom()這個(gè)系統(tǒng)調(diào)用,kernel就開始了IO的第一個(gè)階段:準(zhǔn)備數(shù)據(jù)(對(duì)于網(wǎng)絡(luò)IO來說瞬沦,很多時(shí)候數(shù)據(jù)在一開始還沒有到達(dá)太伊。比如,還沒有收到一個(gè)完整的UDP包逛钻。這個(gè)時(shí)候kernel就要等待足夠的數(shù)據(jù)到來)僚焦。這個(gè)過程需要等待,也就是說數(shù)據(jù)被拷貝到操作系統(tǒng)內(nèi)核的緩沖區(qū)中是需要一個(gè)過程的绣的。而在用戶進(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)行起來踢故。所以文黎,blocking IO的特點(diǎn)就是在IO執(zhí)行的兩個(gè)階段都被block了。
優(yōu)點(diǎn):
? ? 能夠及時(shí)返回?cái)?shù)據(jù)殿较,無延遲耸峭;
? ? 對(duì)內(nèi)核開發(fā)者來說這是省事了;
缺點(diǎn):
? ?對(duì)用戶來說處于等待就要付出性能的代價(jià)了淋纲;
2.2同步非阻塞IO(nonblocking ?IO)
2.2.1場(chǎng)景描述
我女友不甘心白白在這等劳闹,又想去逛商場(chǎng),又擔(dān)心飯好了洽瞬。所以我們逛一會(huì)本涕,回來詢問服務(wù)員飯好了沒有,來來回回好多次伙窃,飯都還沒吃都快累死了啦菩颖。這就是非阻塞。需要不斷的詢問为障,是否準(zhǔn)備好了晦闰。
2.2.2網(wǎng)絡(luò)模型
同步非阻塞就是 “每隔一會(huì)兒瞄一眼進(jìn)度條”的輪詢(polling)方式放祟。在這種模型中,設(shè)備是以非阻塞的形式打開的呻右。這意味著IO操作不會(huì)立即完成舞竿,read操作可能會(huì)返回一個(gè)錯(cuò)誤代碼,說明這個(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光顧"。
也就是說非阻塞的recvform系統(tǒng)調(diào)用調(diào)用之后芜赌,進(jìn)程并沒有被阻塞仰挣,內(nèi)核馬上返回給進(jìn)程,如果數(shù)據(jù)還沒準(zhǔn)備好缠沈,此時(shí)會(huì)返回一個(gè)error膘壶。進(jìn)程在返回之后,可以干點(diǎn)別的事情洲愤,然后再發(fā)起recvform系統(tǒng)調(diào)用颓芭。重復(fù)上面的過程,循環(huán)往復(fù)的進(jìn)行recvform系統(tǒng)調(diào)用柬赐。這個(gè)過程通常被稱之為輪詢亡问。輪詢檢查內(nèi)核數(shù)據(jù),直到數(shù)據(jù)準(zhǔn)備好肛宋,再拷貝數(shù)據(jù)到進(jìn)程州藕,進(jìn)行數(shù)據(jù)處理。需要注意酝陈,拷貝數(shù)據(jù)整個(gè)過程床玻,進(jìn)程仍然是屬于阻塞的狀態(tài)。
在linux下沉帮,可以通過設(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ù)還沒有準(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)存询一,然后返回隐孽。所以,nonblocking ?IO的特點(diǎn)是用戶進(jìn)程需要不斷的主動(dòng)詢問kernel數(shù)據(jù)好了沒有健蕊。
同步非阻塞方式相比同步阻塞方式:
優(yōu)點(diǎn):能夠在等待任務(wù)完成的時(shí)間里干其他活了(包括提交其他任務(wù)菱阵,也就是“后臺(tái)” 可以有多個(gè)任務(wù)在同時(shí)執(zhí)行)。
缺點(diǎn):任務(wù)完成的響應(yīng)延遲增大了缩功,因?yàn)槊窟^一段時(shí)間才去輪詢一次read操作晴及,而任務(wù)可能在兩次輪詢之間的任意時(shí)間完成。這會(huì)導(dǎo)致整體數(shù)據(jù)吞吐量的降低嫡锌。
(ps:為什么這里還是叫做同步呢?因?yàn)闊o論是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è)方案差不多虑稼,餐廳安裝了電子屏幕用來顯示點(diǎn)餐的狀態(tài),這樣我和女友逛街一會(huì)势木,回來就不用去詢問服務(wù)員了蛛倦,直接看電子屏幕就可以了。這樣每個(gè)人的餐是否好了跟压,都直接看電子屏幕就可以了胰蝠,這就是典型的IO多路復(fù)用。
(PS:多了個(gè)電子屏幕)
2.3.2網(wǎng)絡(luò)模型
由于同步非阻塞方式需要不斷主動(dòng)輪詢震蒋,輪詢占據(jù)了很大一部分過程,輪詢會(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)聽,當(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è)過程是阻塞的地来。select或poll調(diào)用之后,會(huì)阻塞進(jìn)程峡竣,與blocking ?IO阻塞不同在于靠抑,此時(shí)的select不是等到socket數(shù)據(jù)全部到達(dá)再處理,而是有了一部分?jǐn)?shù)據(jù)就會(huì)調(diào)用用戶進(jìn)程來處理。如何知道有一部分?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è)寫操作的I/O函數(shù)進(jìn)行檢測(cè),直到有數(shù)據(jù)可讀或可寫時(shí)(注意不是全部數(shù)據(jù)可讀或可寫)眠蚂,才真正調(diào)用I/O操作函數(shù)煞聪。
對(duì)于多路復(fù)用,也就是輪詢多個(gè)socket逝慧。多路復(fù)用既然可以處理多個(gè)IO昔脯,也就帶來了新的問題,多個(gè)IO之間的順序變得不確定了笛臣,當(dāng)然也可以針對(duì)不同的編號(hào)云稚。具體流程,如下圖所示:
2.3.3流程描述
IO multiplexing就是我們說的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)是通過一種機(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í)并沒有太大的不同卫病,事實(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全封。所以IO多路復(fù)用是阻塞在select马昙,epoll這樣的系統(tǒng)調(diào)用之上,而沒有阻塞在真正的I/O系統(tǒng)調(diào)用如recvfrom之上刹悴。
在I/O編程過程中行楞,當(dāng)需要同時(shí)處理多個(gè)客戶端接入請(qǐng)求時(shí),可以利用多線程或者I/O多路復(fù)用技術(shù)進(jìn)行處理土匀。I/O多路復(fù)用技術(shù)通過把多個(gè)I/O的阻塞復(fù)用到同一個(gè)select的阻塞上子房,從而使得系統(tǒng)在單線程的情況下可以同時(shí)處理多個(gè)客戶端請(qǐng)求。與傳統(tǒng)的多線程/多進(jìn)程模型比就轧,I/O多路復(fù)用的最大優(yōu)勢(shì)是系統(tǒng)開銷小择份,系統(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ài)或者多個(gè)連接狀態(tài)的套接字携丁。
服務(wù)器需要同時(shí)處理多種網(wǎng)絡(luò)協(xié)議的套接字琢歇。
了解了前面三種IO模式,在用戶進(jìn)程進(jìn)行系統(tǒng)調(diào)用的時(shí)候梦鉴,他們?cè)诘却龜?shù)據(jù)到來的時(shí)候李茫,處理的方式不一樣,直接等待肥橙,輪詢魄宏,select或poll輪詢,兩個(gè)階段過程:
第一個(gè)階段有的阻塞存筏,有的不阻塞宠互,有的可以阻塞又可以不阻塞。
第二個(gè)階段都是阻塞的椭坚。
從整個(gè)IO過程來看予跌,他們都是順序執(zhí)行的,因此可以歸為同步模型(synchronous)善茎。都是進(jìn)程主動(dòng)等待且向內(nèi)核檢查狀態(tài)券册。【此句很重要!K副骸航邢!】
高并發(fā)的程序一般使用同步非阻塞方式而非多線程+同步阻塞方式。要理解這一點(diǎn)骄蝇,首先要扯到并發(fā)和并行的區(qū)別膳殷。比如去某部門辦事需要依次去幾個(gè)窗口,辦事大廳里的人數(shù)就是并發(fā)數(shù)乞榨,而窗口個(gè)數(shù)就是并行度秽之。也就是說并發(fā)數(shù)是指同時(shí)進(jìn)行的任務(wù)數(shù)(如同時(shí)服務(wù)的HTTP請(qǐng)求),而并行數(shù)是可以同時(shí)工作的物理資源數(shù)量(如CPU核數(shù))吃既。通過合理調(diào)度任務(wù)的不同階段考榨,并發(fā)數(shù)可以遠(yuǎn)遠(yuǎn)大于并行度,這就是區(qū)區(qū)幾個(gè)CPU可以支持上萬個(gè)用戶并發(fā)請(qǐng)求的奧秘鹦倚。在這種高并發(fā)的情況下河质,為每個(gè)任務(wù)(用戶請(qǐng)求)創(chuàng)建一個(gè)進(jìn)程或線程的開銷非常大。而同步非阻塞方式可以把多個(gè)IO請(qǐng)求丟到后臺(tái)去震叙,這就可以在一個(gè)進(jìn)程里服務(wù)大量的并發(fā)IO請(qǐng)求掀鹅。
注意:IO多路復(fù)用是同步阻塞模型還是異步阻塞模型,在此給大家分析下:
此處仍然不太清楚的媒楼,強(qiáng)烈建議大家在細(xì)究《聊聊同步乐尊、異步、阻塞與非阻塞》中講同步與異步的根本性區(qū)別划址,同步是需要主動(dòng)等待消息通知扔嵌,而異步則是被動(dòng)接收消息通知,通過回調(diào)夺颤、通知痢缎、狀態(tà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ù)俭驮。過程如下圖所示:
2.5異步非阻塞IO(asynchronous ? IO)
2.5.1場(chǎng)景描述
女友不想逛街,又餐廳太吵了,回家好好休息一下混萝。于是我們叫外賣遗遵,打個(gè)電話點(diǎn)餐,然后我和女友可以在家好好休息一下逸嘀,飯好了送貨員送到家里來车要。這就是典型的異步,只需要打個(gè)電話說一下崭倘,然后可以做自己的事情翼岁,飯好了就送來了。
2.5.2網(wǎng)絡(luò)模型
相對(duì)于同步IO司光,異步IO不是順序執(zhí)行琅坡。用戶進(jìn)程進(jìn)行aio_read系統(tǒng)調(diào)用之后,無論內(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庫函數(shù)實(shí)現(xiàn)異步,但是用的很少啡直。目前有很多開源的異步IO庫烁涌,例如libevent、libev付枫、libuv烹玉。異步過程如下圖所示:
2.5.3流程描述
用戶進(jìn)程發(fā)起aio_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或執(zhí)行一個(gè)基于線程的回調(diào)函數(shù)來完成這次IO處理過程凡简,告訴它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)來的黎烈,因此跟中斷處理程序一樣,有很多事情是不能做的匀谣,因此保險(xiǎn)起見照棋,一般是把事件“登記” 一下放進(jìn)隊(duì)列,然后返回該進(jìn)程原來在做的事武翎。如果這個(gè)進(jìn)程正在內(nèi)核態(tài)忙著做別的事烈炭,例如以同步阻塞方式讀寫磁盤,那就只好把這個(gè)通知掛起來了后频,等到內(nèi)核態(tài)的事情忙完了梳庆,快要回到用戶態(tài)的時(shí)候,再觸發(fā)信號(hào)通知卑惜。
如果這個(gè)進(jìn)程現(xiàn)在被掛起了膏执,例如無事可做sleep了,那就把這個(gè)進(jìn)程喚醒露久,下次有CPU空閑的時(shí)候更米,就會(huì)調(diào)度到這個(gè)進(jìn)程,觸發(fā)信號(hào)通知毫痕。
異步API說來輕巧征峦,做來難,這主要是對(duì)API的實(shí)現(xiàn)者而言的消请。Linux的異步IO(AIO)支持是2.6.22才引入的栏笆,還有很多系統(tǒng)調(diào)用不支持異步IO。Linux的異步IO最初是為數(shù)據(jù)庫設(shè)計(jì)的臊泰,因此通過異步IO的讀寫操作不會(huì)被緩存或緩沖蛉加,這就無法利用操作系統(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操作開始時(shí)阻塞應(yīng)用程序莽鸭。這意味著不可能同時(shí)重疊進(jìn)行處理和IO操作吗伤。
同步非阻塞模型允許處理和IO操作重疊進(jìn)行,但是這需要應(yīng)用程序根據(jù)重現(xiàn)的規(guī)則來檢查IO操作的狀態(tài)硫眨。
這樣就剩下異步非阻塞IO了,它允許處理和IO操作重疊進(jìn)行巢块,包括IO操作完成的通知礁阁。
IO多路復(fù)用除了需要阻塞之外,select函數(shù)所提供的功能(異步阻塞IO)與AIO類似族奢。不過姥闭,它是對(duì)通知事件進(jìn)行阻塞,而不是對(duì)IO調(diào)用進(jìn)行阻塞越走。
2.6關(guān)于異步阻塞
有時(shí)我們的API只提供異步通知方式棚品,例如在node.js里,但業(yè)務(wù)邏輯需要的是做完一件事后做另一件事廊敌,例如數(shù)據(jù)庫連接初始化后才能開始接受用戶的HTTP請(qǐng)求铜跑。這樣的業(yè)務(wù)邏輯就需要調(diào)用者是以阻塞方式來工作。
為了在異步環(huán)境里模擬 “順序執(zhí)行” ?的效果骡澈,就需要把同步代碼轉(zhuǎn)換成異步形式锅纺,這稱為CPS(Continuation Passing Style)變換。BYVoid大神的continuation.js庫就是一個(gè)CPS變換的工具肋殴。用戶只需用比較符合人類常理的同步方式書寫代碼囤锉,CPS變換器會(huì)把它轉(zhuǎn)換成層層嵌套的異步回調(diào)形式。
另外一種使用阻塞方式的理由是降低響應(yīng)延遲护锤。如果采用非阻塞方式官地,一個(gè)任務(wù)A被提交到后臺(tái),就開始做另一件事B烙懦,但B還沒做完驱入,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ū)別
在說明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ì)說,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的诈泼。而asynchronousIO則不一樣,當(dāng)進(jìn)程發(fā)起IO操作之后煤禽,就直接返回再也不理睬了铐达,直到kernel發(fā)送一個(gè)信號(hào),告訴進(jìn)程說IO完成檬果。在這整個(gè)過程中瓮孙,進(jìn)程完全沒有被block。各個(gè)IO
Model的比較如圖所示:
通過上面的圖片选脊,可以發(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來將數(shù)據(jù)拷貝到用戶內(nèi)存钝的。而asynchronous IO則完全不同翁垂。它就像是用戶進(jìn)程將整個(gè)IO操作交給了他人(kernel)完成铆遭,然后他人做完后發(fā)信號(hào)通知。在此期間沿猜,用戶進(jìn)程不需要去檢查IO操作的狀態(tài)枚荣,也不需要主動(dòng)的去拷貝數(shù)據(jù)。