Node的異步I/O一探

Node的異步I/O

我們?yōu)槭裁葱枰惒絀/O蟀俊?

  • 用戶體驗
    服務器端如果基于同步執(zhí)行的,隨著應用復雜性的增加,響應的總耗時為M+N+...的總時間诫尽,但是異步執(zhí)行的話,總耗時則為M炬守、N牧嫉、...中耗時最長的一個,能夠更快速響應資源减途,讓前端的體驗更好
  • 資源分配
    Node利用單線程酣藻,遠離多線程、狀態(tài)同步等問題鳍置,利用異步I/O辽剧,讓單線程遠離阻塞,以更好地利用CPU

異步I/O與非阻塞I/O

操作系統(tǒng)內(nèi)核對于I/O只有兩種方式:阻塞與非阻塞
阻塞I/O: 調(diào)用之后一定要等到系統(tǒng)內(nèi)核層面完成所有操作后墓捻,調(diào)用才結(jié)束
阻塞I/O造成CPU等待I/O抖仅,浪費等待時間,CPU的處理能力不能得到充分利用砖第。
非阻塞I/O:調(diào)用后會立刻不帶數(shù)據(jù)立刻返回(返回的僅僅是當前調(diào)用的狀態(tài))撤卢,要獲取數(shù)據(jù),還需要通過文件描述符進行再次讀取梧兼。
應用程序需要重復調(diào)用I/O操作來確認是否完成放吩,稱為輪詢

主要的輪詢技術(shù)

  • read。最原始羽杰,性能最低的一種渡紫,通過重復調(diào)用來檢查I/O的狀態(tài)來完成完整數(shù)據(jù)的讀取
  • select。通過對文件描述符的事件狀態(tài)來進行判斷(僅輪詢一次即可考赛,可以同時檢查1024個文件描述符惕澎,但是會持續(xù)等待到數(shù)據(jù)讀取完成為止)
  • poll。與select類似颜骤,但是采用鏈表的方式來存儲狀態(tài)唧喉。其次它能避免不需要的檢查
  • epoll。該方案是Linux下效率最高的的I/O事件通知機制忍抽,在進入輪詢的時候如果沒有檢查到I/O事件八孝,將會進行休眠,直到事件發(fā)生將它喚醒鸠项。不會浪費CPU干跛,執(zhí)行效率較高。
  • kquue祟绊。與epoll類似楼入,不過僅在FreeBSD系統(tǒng)下存在

現(xiàn)實的異步I/O

通過讓部分線程進行阻塞I/O或者非阻塞I/O加輪詢技術(shù)來完成數(shù)據(jù)獲取哥捕,讓一個線程進行計算處理,通過線程之間的通信將I/O得到的數(shù)據(jù)進行傳遞浅辙,實現(xiàn)異步I/O
因此扭弧,Javascript只需在單線程(主線程)中執(zhí)行,內(nèi)部完成I/O任務的另有線程池记舆。


IMG_20171001_170208.jpg

Node的異步I/O

事件循環(huán)

在進程啟動時鸽捻,Node便會創(chuàng)建一個類似while(true)的循環(huán),每執(zhí)行一次循環(huán)體的過程稱為Tick泽腮,每個Tick的過程就是查看是否有事件待處理御蒲,如果有,就取出事件及其相關(guān)的回調(diào)函數(shù)進而執(zhí)行诊赊,然后進入下個循環(huán)厚满,如果不再有事件處理,則跳出流程


IMG_20171001_190115.jpg
觀察者

每個Tick過程中碧磅,由一個或多個觀察者來判斷是否有要處理的事件碘箍。
異步I/O、網(wǎng)絡請求等是事件的生產(chǎn)者鲸郊,源源不斷為Node提供不同類型的事件丰榴,這些事件被傳遞到對應的觀察者那里,事件循環(huán)則從觀察者那里取出事件并處里

請求對象

事實上秆撮,從Javascript發(fā)起調(diào)用到內(nèi)核執(zhí)行完成I/O操作的過渡過程中四濒,存在一種中間產(chǎn)物,叫做請求對象
拿fs.open()作為例子
(1)fs.open()根據(jù)路徑和參數(shù)去打開一個.cc文件(C++內(nèi)建模塊)职辨,從而得到一個文件描述符
(2)然后這個.cc文件經(jīng)過libuv平臺判斷調(diào)用對應平臺的uv_fs_open()方法
(3)在uv_fs_open()調(diào)用過程中盗蟆,創(chuàng)建了一個FSReqWrap請求對象,而從Javascript層傳入的參數(shù)和當前方法都會封裝到這個請求對象上舒裤,而回調(diào)函數(shù)則被設置在這個對象的oncomplete_sym屬性上

req_wrap->object->Set(oncpmplete_sym,callback);

(4)對象包裝完成后喳资,在Windows下,調(diào)用QueueUserWorkItem()方法將這個FSReaWrap對象推入線程池中等待執(zhí)行

QueueUserWorkItem(&uv_fs_thread_proc,req,WT_EXECUTEDEFAULT)
/*
接收三個參數(shù):
①將要執(zhí)行的方法的引用
②將要執(zhí)行的方法運行時所需要的參數(shù)
③執(zhí)行的標志
*/

(5)當有可用線程時腾供,會調(diào)用uv_fs_thread_proc()方法骨饿,這個方法會根據(jù)傳入?yún)?shù)的類型調(diào)用相應的底層函數(shù)。以uv_fs_open()為例台腥,實際上調(diào)用的是fs_open()方法
(6)至此,Javascript調(diào)用立即返回绒北,由Javascript層面發(fā)起的異步調(diào)用的第一階段就此結(jié)束黎侈,因此javascript可繼續(xù)執(zhí)行其他操作,從而達到異步的目的

執(zhí)行回調(diào)(處理請求對象)

(1)線程中的I/O操作調(diào)用完畢之后闷游,會將獲取的結(jié)果儲存在req->result屬性上峻汉,然后調(diào)用PostQueuedCompletionStatus()通知IOCP贴汪,告知當前對象操作已經(jīng)完成

PostQueuedCompletionStatus()方法的作用是向IOCP提交執(zhí)行狀態(tài),并把線程歸還線程池休吠,這個狀態(tài)可以通過GetQueuedCompletionStatus()提取
(2)每次Tick的執(zhí)行中扳埂,觀察者會調(diào)用IOCP相關(guān)的GetQueuedCompletionStatus()檢查線程池中是否有執(zhí)行完的請求,如果存在瘤礁,會把請求對戲那個加入到I/O觀察者的隊列中阳懂,然后將其當做事件處理
(3)取出請求對象的result屬性作為參數(shù),取出oncomplete_sym屬性(傳入的回調(diào)函數(shù))作為方法柜思,然后調(diào)用執(zhí)行岩调,以此達到調(diào)用Javascript中傳入的回調(diào)函數(shù)的目的。


IMG_20171001_190136.jpg
總結(jié)

一個異步I/O經(jīng)歷了請求對象赡盘、I/O線程池号枕、觀察者事件循環(huán)這四個步驟陨享,構(gòu)成了異步I/O模型的基本要素葱淳。windows下主要通過IOCP來向系統(tǒng)內(nèi)核發(fā)送I/O調(diào)用和從內(nèi)核獲取已完成的I/O操作,配以事件循環(huán)抛姑,以此完成異步I/O的過程赞厕。

非I/O的異步API

  • 定時器
    調(diào)用setTimeout()或者setInterval()創(chuàng)建的定時器會被插入到定時器觀察者內(nèi)部的一個紅黑樹中,每次Tick執(zhí)行時途戒,會從該紅黑樹中迭代取出定時器對象坑傅,檢查是否超過定時事件,如果超過就形成一個事件喷斋,它的回調(diào)函數(shù)將會被推入handles中排隊等候被執(zhí)行
  • process.nextTick()
    每次調(diào)用process.nextTick()方法唁毒,只會將回調(diào)函數(shù)放入隊列中,在下一輪Tick取出執(zhí)行星爪。復雜度更低浆西,性能比setTimeout更高效。
  • setImmediate()
    與process.nextTick()類似顽腾,但process.nextTick中的回調(diào)函數(shù)執(zhí)行的優(yōu)先級要高于setImmediate()近零,因為事件循環(huán)對觀察者的檢查是有先后順序的,process.nextTick()屬于idle觀察者抄肖,setImmeditate屬于check觀察者久信。

以上參考《深入淺出Node.js》一書

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市漓摩,隨后出現(xiàn)的幾起案子裙士,更是在濱河造成了極大的恐慌,老刑警劉巖管毙,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件腿椎,死亡現(xiàn)場離奇詭異桌硫,居然都是意外死亡,警方通過查閱死者的電腦和手機啃炸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進店門铆隘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人南用,你說我怎么就攤上這事膀钠。” “怎么了训枢?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵托修,是天一觀的道長。 經(jīng)常有香客問我恒界,道長睦刃,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任十酣,我火速辦了婚禮涩拙,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘耸采。我一直安慰自己兴泥,他們只是感情好,可當我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布虾宇。 她就那樣靜靜地躺著搓彻,像睡著了一般。 火紅的嫁衣襯著肌膚如雪嘱朽。 梳的紋絲不亂的頭發(fā)上旭贬,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天,我揣著相機與錄音搪泳,去河邊找鬼稀轨。 笑死,一個胖子當著我的面吹牛岸军,可吹牛的內(nèi)容都是我干的奋刽。 我是一名探鬼主播,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼艰赞,長吁一口氣:“原來是場噩夢啊……” “哼佣谐!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起方妖,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤台谍,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體趁蕊,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年仔役,在試婚紗的時候發(fā)現(xiàn)自己被綠了掷伙。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡又兵,死狀恐怖任柜,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情沛厨,我是刑警寧澤宙地,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站逆皮,受9級特大地震影響宅粥,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜电谣,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一秽梅、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧剿牺,春花似錦企垦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至湃崩,卻和暖如春荧降,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背竹习。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工誊抛, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人整陌。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓拗窃,卻偏偏與公主長得像,于是被迫代替她去往敵國和親泌辫。 傳聞我的和親對象是個殘疾皇子随夸,可洞房花燭夜當晚...
    茶點故事閱讀 42,802評論 2 345

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