阻塞款违、非阻塞、同步宙攻、異步

在談及網(wǎng)絡(luò)IO的時(shí)候總避不開阻塞奠货、非阻塞介褥、同步座掘、異步、IO多路復(fù)用柔滔、select溢陪、poll、epoll等這幾個(gè)詞語睛廊。在面試的時(shí)候也會(huì)被經(jīng)常問到這幾個(gè)的區(qū)別形真。本文就來講一下這幾個(gè)詞語的含義、區(qū)別以及使用方式超全。

Unix網(wǎng)絡(luò)編程一書中作者給出了五種IO模型:

1咆霜、BlockingIO - 阻塞IO

2、NoneBlockingIO - 非阻塞IO

3嘶朱、IO multiplexing - IO多路復(fù)用

4蛾坯、signal driven IO - 信號(hào)驅(qū)動(dòng)IO

5、asynchronous IO - 異步IO

這五種IO模型中前四個(gè)都是同步的IO疏遏,只有最后一個(gè)是異步IO脉课。信號(hào)驅(qū)動(dòng)IO使用的比較少,重點(diǎn)介紹其他幾種IO以及在Java中的應(yīng)用财异。

阻塞倘零、非阻塞、同步戳寸、異步以及IO多路復(fù)用

在進(jìn)行網(wǎng)絡(luò)IO的時(shí)候會(huì)涉及到用戶態(tài)和內(nèi)核態(tài)呈驶,并且在用戶態(tài)和內(nèi)核態(tài)之間會(huì)發(fā)生數(shù)據(jù)交換,從這個(gè)角度來說我們可以把IO抽象成兩個(gè)階段:1疫鹊、用戶態(tài)等待內(nèi)核態(tài)數(shù)據(jù)準(zhǔn)備好袖瞻,2跌穗、將數(shù)據(jù)從內(nèi)核態(tài)拷貝到用戶態(tài)。之所以會(huì)有同步虏辫、異步蚌吸、阻塞和非阻塞這幾種說法就是根據(jù)程序在這兩個(gè)階段的處理方式不同而產(chǎn)生的。

同步阻塞

顯示大圖

當(dāng)在用戶態(tài)調(diào)用read操作的時(shí)候砌庄,如果這時(shí)候kernel還沒有準(zhǔn)備好數(shù)據(jù)羹唠,那么用戶態(tài)會(huì)一直阻塞等待,直到有數(shù)據(jù)返回娄昆。當(dāng)kernel準(zhǔn)備好數(shù)據(jù)之后佩微,用戶態(tài)繼續(xù)等待kernel把數(shù)據(jù)從內(nèi)核態(tài)拷貝到用戶態(tài)之后才可以使用。這里會(huì)發(fā)生兩種等待:一個(gè)是用戶態(tài)等待kernel有數(shù)據(jù)可以讀萌焰,另外一個(gè)是當(dāng)有數(shù)據(jù)可讀時(shí)用戶態(tài)等待kernel把數(shù)據(jù)拷貝到用戶態(tài)哺眯。

在Java中同步阻塞的實(shí)現(xiàn)對(duì)應(yīng)的是傳統(tǒng)的文件IO操作以及Socket的accept的過程。在Socket調(diào)用accept的時(shí)候扒俯,程序會(huì)一直等待知道有描述符就緒奶卓,并且把就緒的數(shù)據(jù)拷貝到用戶態(tài),然后程序中就可以拿到對(duì)應(yīng)的數(shù)據(jù)撼玄。

同步非阻塞

對(duì)比第一張同步阻塞IO的圖就會(huì)發(fā)現(xiàn)夺姑,在同步非阻塞模型下第一個(gè)階段是不等待的,無論有沒有數(shù)據(jù)準(zhǔn)備好掌猛,都是立即返回盏浙。第二個(gè)階段仍然是需要等待的,用戶態(tài)需要等待內(nèi)核態(tài)把數(shù)據(jù)拷貝過來才能使用荔茬。對(duì)于同步非阻塞模式的處理废膘,需要每隔一段時(shí)間就去詢問一下內(nèi)核數(shù)據(jù)是不是可以讀了,如果內(nèi)核說可以慕蔚,那么就開始第二階段等待丐黄。

IO多路復(fù)用

IO多路復(fù)用也是同步的。

IO多路復(fù)用的方式看起來跟同步阻塞是一樣的坊萝,兩個(gè)階段都是阻塞的孵稽,但是IO多路復(fù)用可以實(shí)現(xiàn)以較小的代價(jià)同時(shí)監(jiān)聽多個(gè)IO。通常情況下是通過一個(gè)線程來同時(shí)監(jiān)聽多個(gè)描述符十偶,只要任何一個(gè)滿足就緒條件菩鲜,那么內(nèi)核態(tài)就返回。IO多路復(fù)用使得傳統(tǒng)的每請(qǐng)求每線程的處理方式得到解耦惦积,一個(gè)線程可以同時(shí)處理多個(gè)IO請(qǐng)求接校,然后交到后面的線程池里處理,這也是netty等框架的處理方式,所謂的reactor模式蛛勉。IO多路復(fù)用的實(shí)現(xiàn)依賴于操作系統(tǒng)的select鹿寻、poll和epoll,后面會(huì)詳細(xì)介紹這幾個(gè)系統(tǒng)調(diào)用诽凌。

IO多路復(fù)用在Java中的實(shí)現(xiàn)方式是在Socket編程中使用非阻塞模式毡熏,然后配置感興趣的事件,通過調(diào)用select函數(shù)來實(shí)現(xiàn)侣诵。select函數(shù)就是對(duì)應(yīng)的第一個(gè)階段痢法。如果給select配置了超時(shí)參數(shù),在指定時(shí)間內(nèi)沒有感興趣事件發(fā)生的話杜顺,select調(diào)用也會(huì)返回财搁,這也是為什么要做非阻塞模式下運(yùn)行。

異步IO

異步模式下躬络,前面提到的兩個(gè)階段都不會(huì)等待尖奔。使用異步模式,用戶態(tài)調(diào)用read方法的時(shí)候穷当,相當(dāng)于告訴內(nèi)核數(shù)據(jù)發(fā)送給我之后告訴我一聲我先去干別的事情了提茁。在這兩個(gè)階段都不會(huì)等待,只需要在內(nèi)核態(tài)通知數(shù)據(jù)準(zhǔn)備好之后使用即可膘滨。通常情況下使用異步模式都會(huì)使用callback甘凭,當(dāng)數(shù)據(jù)可用之后執(zhí)行callback函數(shù)稀拐。

IO多路復(fù)用

現(xiàn)在用Java開發(fā)的網(wǎng)絡(luò)服務(wù)器通常采用IO多路復(fù)用的方式來加快網(wǎng)絡(luò)IO操作火邓,例如Netty、Tomcat等德撬。IO多路復(fù)用的基礎(chǔ)是select铲咨、poll和epoll。這三個(gè)函數(shù)是從操作系統(tǒng)的角度上支持的IO多路復(fù)用的操作蜓洪,下面就分別來看一下這三個(gè)函數(shù)纤勒。

select

函數(shù)簽名如下:

int select(int maxfdp1, fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timeval *timeout)

maxfdp1為指定的待監(jiān)聽的描述符的個(gè)數(shù),因?yàn)槊枋龇菑?開始的隆檀,所以需要加1

readset為要監(jiān)聽的讀描述符

writeset為要監(jiān)聽的寫描述符

exceptset為要監(jiān)聽的異常描述符

timeout監(jiān)聽沒有準(zhǔn)備好的描述符的話摇天,多久可以返回,支持按照秒或者毫秒來配置時(shí)間

select操作的邏輯是首先將要監(jiān)聽的讀恐仑、寫以及異常描述符拷貝到內(nèi)核空間泉坐,然后遍歷所有的描述符,如果有感興趣的事件發(fā)生裳仆,那么就返回腕让。

select在使用的過程中有三個(gè)問題:

1、被監(jiān)控的fds(描述符)集合限制為1024歧斟,1024太小了

2纯丸、需要將描述符集合從用戶空間拷貝到內(nèi)核空間

3偏形、當(dāng)有描述符可操作的時(shí)候都需要遍歷一下整個(gè)描述符集合才能知道哪個(gè)是可操作的,效率很低觉鼻。

poll

函數(shù)簽名如下:

int poll(struct pollfd[] fds, unsigned int nfds, int timeout);

poll操作與select操作類似俊扭,仍舊避免不了描述符從用戶空間拷貝到內(nèi)核空間,但是poll不再有1024個(gè)描述符的限制坠陈。對(duì)于事件的觸發(fā)通知還是使用遍歷所有描述符的方式统扳,因此在大量連接的情況下也存在遍歷低效的問題。poll函數(shù)在傳遞參數(shù)的時(shí)候統(tǒng)一的將要監(jiān)聽的描述符和事件封裝在了pollfd結(jié)構(gòu)體數(shù)組中畅姊。

epoll

epoll有三個(gè)方法:epoll_create咒钟、epoll_ctl和epoll_wait。epoll_create是創(chuàng)建一個(gè)epoll句柄若未;epoll_ctl是注冊(cè)要監(jiān)聽的事件類型朱嘴;epoll_wait則是等待事件的產(chǎn)生。 通過這三個(gè)方法epoll解決了select的三個(gè)問題粗合。

1萍嬉、1024數(shù)量限制的問題

通過epoll_create方法來創(chuàng)建一個(gè)epoll句柄,這個(gè)句柄監(jiān)聽的描述符的數(shù)量不再有限制隙疚。

2壤追、文件描述符頻繁從用戶空間拷貝到內(nèi)核空間的問題

通過觀察select的操作會(huì)發(fā)現(xiàn)描述符從用戶空間到內(nèi)核空間拷貝發(fā)生在調(diào)用select方法的時(shí)候,只要沒有注冊(cè)新的事件或者取消注冊(cè)事件供屉,每次拷貝的描述符都是一樣的行冰。因此epoll引入了epoll_ctl調(diào)用,該方法用于注冊(cè)新事件和取消注冊(cè)事件伶丐。而在epoll_wait的時(shí)候并不會(huì)拷貝描述符悼做,描述符始終存在于內(nèi)核空間,當(dāng)需要修改的時(shí)候只要調(diào)用epoll_ctl修改一下內(nèi)核的描述符即可哗魂。如此一來便省去了描述符來回拷貝的開銷肛走。

3、文件描述符可操作的時(shí)候遍歷整個(gè)描述符集合的問題

在調(diào)用epoll_ctl注冊(cè)感興趣的事件的時(shí)候录别,實(shí)際上會(huì)為設(shè)置的事件添加一個(gè)回調(diào)函數(shù)朽色,當(dāng)對(duì)應(yīng)的感興趣的事件發(fā)生的時(shí)候,回調(diào)函數(shù)就會(huì)觸發(fā)组题,然后將自己加到一個(gè)鏈表中葫男。epoll_wait函數(shù)的作用就是去查看這個(gè)鏈表中有沒有已經(jīng)準(zhǔn)備就緒的事件,如果有的話就通知應(yīng)用程序處理往踢,如此操作epoll_wait只需要遍歷就緒的事件描述符即可腾誉。

epoll在Java中的使用

目前針對(duì)Java服務(wù)器的非阻塞編程基本都是基于epoll的。在進(jìn)行非阻塞編程的時(shí)候有兩個(gè)步驟:1、注冊(cè)感興趣的事情利职;2趣效、調(diào)用select方法,查找感興趣的事件猪贪。

注冊(cè)感興趣的事件

我們?cè)诰帉慡ocket的非阻塞代碼的時(shí)候需要在Selector上注冊(cè)感興趣的事情跷敬,通常寫法是

serverSocketChannel.register(selector, SelectionKey.XXX)。來看一下這行代碼背后的執(zhí)行邏輯是什么樣的热押。

注冊(cè)的時(shí)候?qū)嶋H執(zhí)行的是EPollSelectorImp西傀。該方法主要有以下三步:

1、implRegister方法桶癣。在fdToKey的Map中插入channel對(duì)應(yīng)的文件描述法和SelectionKey的映射拥褂,當(dāng)做注冊(cè)Channel、關(guān)閉Channel牙寞、取消注冊(cè)等操作是都是操作此Map饺鹃。

2、往pollWrapper[Epoll實(shí)例]中放入channel實(shí)例间雀。

3悔详、往keys[HashSet]中放入SelectionKey

select方法

通過Java的Selector.select方法來獲取準(zhǔn)備好的鍵的時(shí)候?qū)嶋H執(zhí)行的代碼如下:

首先調(diào)用EPollArrayWrapper的poll方法,該方法做兩件事:1惹挟、調(diào)用epollCtl方法向epoll中注冊(cè)感興趣的事件茄螃;2、調(diào)用epollWait方法返回已就緒的文件描述符集合

然后調(diào)用updateSelectedKeys方法調(diào)用把epoll中就緒的文件描述符加到ready隊(duì)列中等待上層應(yīng)用處理, updateSelectedKeys通過fdToKey查找文件描述符對(duì)應(yīng)的SelectionKey连锯,并在SelectionKey對(duì)應(yīng)的channel中添加對(duì)應(yīng)的事件到ready隊(duì)列归苍。

水平觸發(fā)LT與邊緣觸發(fā)ET

epoll支持兩種觸發(fā)模式,分別是水平觸發(fā)和邊緣觸發(fā)萎庭。

LT是缺省的工作方式霜医,并且同時(shí)支持block和no-block socket。在這種做法中驳规,內(nèi)核告訴你一個(gè)文件描述符是否就緒了,然后你可以對(duì)這個(gè)就緒的fd進(jìn)行IO操作署海。如果你不作任何操作吗购,內(nèi)核還是會(huì)繼續(xù)通知你的。

ET是高速工作方式砸狞,只支持no-block socket捻勉。在這種模式下,當(dāng)描述符從未就緒變?yōu)榫途w時(shí)刀森,內(nèi)核會(huì)通知你一次踱启,并且除非你做了某些操作導(dǎo)致那個(gè)文件描述符不再為就緒狀態(tài)了,否則不會(huì)再次發(fā)送通知。

可以看到埠偿,本來內(nèi)核在被DMA中斷透罢,捕獲到IO設(shè)備來數(shù)據(jù)后,只需要查找這個(gè)數(shù)據(jù)屬于哪個(gè)文件描述符冠蒋,進(jìn)而通知線程里等待的函數(shù)即可羽圃,但是,LT要求內(nèi)核在通知階段還要繼續(xù)再掃描一次剛才所建立的內(nèi)核fd和io對(duì)應(yīng)的那個(gè)數(shù)組抖剿,因?yàn)閼?yīng)用程序可能沒有真正去讀上次通知有數(shù)據(jù)后的那些fd朽寞,這種溝通方式效率是很低下的,只是方便編程而已斩郎;

JDK并沒有實(shí)現(xiàn)邊緣觸發(fā)脑融,關(guān)于邊緣觸發(fā)和水平觸發(fā)的差異簡單列舉如下,邊緣觸發(fā)的性能更高缩宜,但編程難度也更高吨掌,netty就重新實(shí)現(xiàn)了Epoll機(jī)制,采用邊緣觸發(fā)方式脓恕;另外像nginx等也采用的是邊緣觸發(fā)膜宋。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市炼幔,隨后出現(xiàn)的幾起案子秋茫,更是在濱河造成了極大的恐慌,老刑警劉巖乃秀,帶你破解...
    沈念sama閱讀 218,386評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件肛著,死亡現(xiàn)場離奇詭異,居然都是意外死亡跺讯,警方通過查閱死者的電腦和手機(jī)枢贿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來刀脏,“玉大人局荚,你說我怎么就攤上這事∮郏” “怎么了耀态?”我有些...
    開封第一講書人閱讀 164,704評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長暂雹。 經(jīng)常有香客問我首装,道長,這世上最難降的妖魔是什么杭跪? 我笑而不...
    開封第一講書人閱讀 58,702評(píng)論 1 294
  • 正文 為了忘掉前任仙逻,我火速辦了婚禮驰吓,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘系奉。我一直安慰自己檬贰,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,716評(píng)論 6 392
  • 文/花漫 我一把揭開白布喜最。 她就那樣靜靜地躺著偎蘸,像睡著了一般。 火紅的嫁衣襯著肌膚如雪瞬内。 梳的紋絲不亂的頭發(fā)上迷雪,一...
    開封第一講書人閱讀 51,573評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音虫蝶,去河邊找鬼章咧。 笑死,一個(gè)胖子當(dāng)著我的面吹牛能真,可吹牛的內(nèi)容都是我干的赁严。 我是一名探鬼主播,決...
    沈念sama閱讀 40,314評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼粉铐,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼疼约!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蝙泼,我...
    開封第一講書人閱讀 39,230評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤程剥,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后汤踏,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體织鲸,經(jīng)...
    沈念sama閱讀 45,680評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,873評(píng)論 3 336
  • 正文 我和宋清朗相戀三年溪胶,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了搂擦。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,991評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡哗脖,死狀恐怖瀑踢,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情懒熙,我是刑警寧澤丘损,帶...
    沈念sama閱讀 35,706評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站工扎,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏衔蹲。R本人自食惡果不足惜肢娘,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,329評(píng)論 3 330
  • 文/蒙蒙 一呈础、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧橱健,春花似錦而钞、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至珊皿,卻和暖如春网缝,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蟋定。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評(píng)論 1 270
  • 我被黑心中介騙來泰國打工粉臊, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人驶兜。 一個(gè)月前我還...
    沈念sama閱讀 48,158評(píng)論 3 370
  • 正文 我出身青樓扼仲,卻偏偏與公主長得像,于是被迫代替她去往敵國和親抄淑。 傳聞我的和親對(duì)象是個(gè)殘疾皇子屠凶,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,941評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容