Node.js事件循環(huán)

Node核心功能是事件循環(huán)朵逝,此概念也多用于JS底層行為以及許多交互系統(tǒng)中腐螟。在許多語言中栏豺,事件模型是在外層的彭沼,但JS事件一直是其語言的核心模塊缔逛,這是因為JS在很多場景下都需要處理與用戶交互的事件。在服務(wù)端沒有網(wǎng)頁DOM對應的那些有限的用戶驅(qū)動型交互事件姓惑,而是在服務(wù)器程序上對應發(fā)生的各種不同的事件,例如HTTP服務(wù)器模塊在用戶發(fā)送請求給web服務(wù)器時會觸發(fā)request事件按脚。

JS利用事件循環(huán)來合理地處理系統(tǒng)各部分的請求于毙。Node采用的方式是,所有I/O事件都應該是非阻塞的辅搬。這意味著需要讓程序暫停操作的HTTP請求唯沮、數(shù)據(jù)庫查詢脖旱、文件讀寫,以及其他事情介蛉,在數(shù)據(jù)返回之前并不暫停執(zhí)行萌庆。這些事件都將獨立運行,然后在數(shù)據(jù)準備好后觸發(fā)一個事件币旧。也就是說Node編程會用到很多回調(diào)函數(shù)践险,來處理各種I/O〈盗猓回調(diào)函數(shù)往往以級聯(lián)的方式嵌入在其他回調(diào)函數(shù)中巍虫,這與瀏覽器編程有所不同。除了用順序的方式設(shè)置好啟動項外鳍刷,大部分代碼都在處理回調(diào)函數(shù)占遥。

針對這種少見的編程風格,我們需要尋找合適服務(wù)器編程的處理模式输瓜。先從事件模型開始瓦胎,大部分人直覺上是理解事件驅(qū)動編程的,因為這和日常生活息息相關(guān)尤揣。

事件驅(qū)動的人們

假設(shè)你在燒飯搔啊,正在切青椒的時候鍋里面的東西開始沸溢出來了,你會暫停切菜把爐火關(guān)小芹缔。你不會在切青椒的同時把爐火關(guān)小坯癣,而是會采用更加安全的方式,通過快速切換工作對象來達到同樣的目的最欠。事件驅(qū)動編程也是同樣的道理示罗。通過讓程序員一次只能為一個回調(diào)函數(shù)編寫處理代碼,可以讓代碼可讀性更強芝硬,而且能夠快速地處理多個任務(wù)蚜点。

事件驅(qū)動的人們

日常生活中,人們習慣于用各種內(nèi)部回調(diào)的方式來處理遇到的事件拌阴。和JS類似绍绘,我們一次只能處理一件事情。這也和JS很像迟赃,能讓事件來驅(qū)動操作很棒陪拘,但它只能以單線程的方式運行,即同一時間只能處理一件事情纤壁。

單線程的概念非常重要左刽,常有人批評Node缺乏并發(fā),也就是沒有利用機器上所有CPU來運行JS酌媒。但是同時在多個CPU上運行程序也有它的問題欠痴,即需要協(xié)調(diào)多個執(zhí)行線程迄靠。要讓多個CPU有效地拆分任務(wù),它們之間需要不停地交換信息喇辽,比如當前執(zhí)行狀態(tài)以及各自完成了那些工作掌挚。雖然這不是不可能,但這么復雜的模型給程序員和系統(tǒng)帶來了很大的工作量菩咨。JS的方式很簡單吠式,同一時刻只有一件事情在操作。Node做的每一件事情都是非阻塞的旦委,所以事件觸發(fā)與Node對其操作的時間間隔是很短的奇徒,因為Node不需要等待諸如磁盤I/O這樣的操作。

事件驅(qū)動的快遞員

以快遞員投遞為例幫助你理解事件循環(huán)缨硝∧Ω疲快遞員的每個快件都是一個事件,他有一堆快件等著要按順序處理查辩,每個快件都要走相應的路徑進行投遞胖笛。路徑就是對此事件的回調(diào)函數(shù)∫说海可憐的是长踊,快遞員只有一雙腿,每次只能走其中一條路徑萍倡。

事件驅(qū)動的快遞員

偶爾身弊,當快遞員在路上行走時,有人會給它派發(fā)另一快件列敲,這就像是投遞途中的回調(diào)函數(shù)阱佛。這種情況下,快遞員會馬上去派送新的快件戴而。此時快遞員會立即切換到新的路徑去投遞凑术。完成后在回到之前的路徑上繼續(xù)工作。

對比快遞員的行為和一般程序的做法所意。假設(shè)web服務(wù)器被請求要從數(shù)據(jù)庫讀取數(shù)據(jù)淮逊,然后返回給用戶。這種情況下扶踊,我們只要處理很少的事件泄鹏。首先,用戶的請求是要web服務(wù)器返回一個網(wǎng)頁秧耗。處理此個初始請求的回調(diào)函數(shù)A會先從請求的對象中確定它要從數(shù)據(jù)庫讀取什么內(nèi)容命满,然后向數(shù)據(jù)庫發(fā)起具體的請求,并傳入一個回調(diào)函數(shù)B供請求完成時使用绣版。處理完請求后胶台,回調(diào)函數(shù)A結(jié)束并返回。當數(shù)據(jù)庫找到需要的內(nèi)容杂抽,再觸發(fā)相應事件诈唬。事件循環(huán)隊列則調(diào)用回調(diào)函數(shù)B,讓它把數(shù)據(jù)發(fā)送給用戶缩麸。

這似乎非常直觀铸磅,這里需要特別注意的是代碼隔斷的地方,這也是過程式編程不會遇到的情況杭朱。因為Node是一個非阻塞的系統(tǒng)阅仔,所以當調(diào)用需要阻塞等待的數(shù)據(jù)庫函數(shù)時,我們會采用回調(diào)函數(shù)替代閑置等待弧械。這就是說八酒,由另外一些函數(shù)來接管這個請求,并在數(shù)據(jù)準備好返回時把它處理掉刃唐。所以我們需要確定回調(diào)函數(shù)所要用到的數(shù)據(jù)能夠有辦法取得羞迷。JS編程通常是利用閉包來實現(xiàn)這個功能的。

事件驅(qū)動的快餐店

為什么Node更加高效呢画饥?想象下在快餐店點餐衔瓮,你站在柜臺前排隊時,服務(wù)員有兩種方式來處理你的點單抖甘,一種是事件驅(qū)動的热鞍,另一種則不是。先采用PHP等許多web平臺所使用的方式衔彻。你點餐時服務(wù)員先招待你薇宠,待你點完后才服務(wù)下一個客人。他輸入完你的單子后可以做以下幾件事情:收款米奸、為你倒飲料等昼接。但是,服務(wù)員還不知道要等多久廚房才能把你的快餐做好悴晰。在傳統(tǒng)web服務(wù)框架下慢睡,每個服務(wù)程序(線程 )每次只能服務(wù)一個請求。唯一增加處理能力的方法是加入更多的線程铡溪。很顯然這樣的做法并不是那么的高效漂辐,服務(wù)員在等待廚房做菜時浪費了很多時間。

事件驅(qū)動的快餐店

顯然現(xiàn)實生活的餐館使用的是更加高效的模式棕硫。你點完菜后髓涯,服務(wù)員會給你一個號碼,在菜做好時通知你哈扮,你可以稱之為回調(diào)號碼纬纪。Node也是這樣工作的蚓再。當I/O一類費時操作開始時,Node會給它們一個回調(diào)引用包各,然后繼續(xù)處理其他已經(jīng)就緒的工作摘仅。比如服務(wù)員可服務(wù)下一個客人,對Node來說則是下一個事件问畅。需要重點關(guān)注的是娃属。當呼叫某位客人來取食物的時候,他們不會處理新客人的需求护姆,反之亦然矾端。通過事件驅(qū)動的運作方式,服務(wù)員能夠最大程度地提高產(chǎn)出卵皂。

在一些小餐館秩铆,廚師和服務(wù)員是同一個人,這種情況下采用事件驅(qū)動并不能提高效率渐裂,因為所有的工作都由同一個人完成豺旬,事件驅(qū)動的架構(gòu)并不能增加價值。如果服務(wù)器的全部或大部分工作是進行運算柒凉,Node并非最理想的模型族阅。

假設(shè)餐館中有兩名服務(wù)員和四位客人。如果服務(wù)員一次只能服務(wù)一位客人膝捞,那么頭兩個客人可最快地拿到食物坦刀,而剩余兩位的體驗會很糟糕。前兩位客人之所以能夠快速地獲得食物是因為服務(wù)員在全力滿足他們的需求蔬咬,這占用了后兩位客人的時間鲤遥。在事件驅(qū)動模型下,頭兩位客人可能需稍微等待一下才能拿到食物林艘,因為服務(wù)員需先處理一下后兩位客人的點單盖奈,但系統(tǒng)的平均等待時間(延遲)將大大降低。

被阻塞的郵遞員

我們給事件循環(huán)模式的郵遞員一封信去投遞狐援,但投遞這封信需要經(jīng)過一扇門钢坦。郵遞員達到目的地,而門卻關(guān)閉著啥酱,所以他只能等待并不停地嘗試進入爹凹。他等待門打開就像進入了死循環(huán)模式。如果在信封隊列里有另外一封信能夠通知某人來打開門镶殷,讓郵遞員進去禾酱,這不就解決問題了嗎?不幸的是,郵遞員正在無休止地等待打開門颤陶,無法抽身去投遞那封信颗管,這是因為打開門的事件在當前回調(diào)事件的外部。如果在回調(diào)函數(shù)內(nèi)部發(fā)起事件指郁,我們知道郵遞員會優(yōu)先把這封信給投遞掉哦忙上,但是當事件是在當前執(zhí)行代碼的外部發(fā)生時,它必須等待正在執(zhí)行的代碼完成之后才會被調(diào)用闲坎。

被阻塞的事件隊列

雖然我們不太會編寫依賴外部條件作為跳出判斷的循環(huán)體,但這展示了Node同時只能處理一件事情的本質(zhì)茬斧,任何一點缺陷都可能導致整個系統(tǒng)混亂腰懂。這也是事件驅(qū)動編程的核心模塊是非阻塞I/O的原因。

var evt = require('events');
var EE = evt.EventEmitter;
var ee = new EE();

var die = false;

ee.on('die', function(){
  die = true;
});

setTimeout(function(){
  ee.emit('die');
}, 100);

while(!die){}

// console.log()永遠不會被調(diào)用项秉,因為while循環(huán)不會讓Node有機會觸發(fā)timeout回調(diào)函數(shù)并發(fā)起die事件绣溜。
console.log('done');

創(chuàng)建HTTP服務(wù)器

var http = require('http');

http.createServer(function(req, res){
  res.writeHead(200, {Content-Type:'text/plain'});
  res.end('Hi\n');
}).listen(8124, '127.0.0.1');

console.log('server running');

通過調(diào)用http庫的工廠方法來創(chuàng)建HTTP服務(wù)器,工廠方法在創(chuàng)建新的HTTP服務(wù)器的同時娄蔼,為request事件綁定了一個回調(diào)函數(shù)怖喻,后者作為createServer()的第一個參數(shù)傳遞進去。當代碼運行的時候會發(fā)生什么有趣的事情呢岁诉?

Node.js運行的第一件事情是把代碼從頭到尾運行一遍锚沸,這可以認為是Node編程的設(shè)置階段。因為我們綁定了一些事件監(jiān)聽器涕癣,所以Node.js不會退出哗蜈,而是等待這些事件被觸發(fā)。若沒有綁定任何事件坠韩,Node.js在運行完代碼后就會立即退出距潘。

當服務(wù)器接收到一個HTTP請求時會進行怎樣的處理呢?Node.js會發(fā)起request事件只搁,因為該事件有對應的回調(diào)函數(shù)綁定在上面音比,回調(diào)函數(shù)會被依次調(diào)用。

假設(shè)網(wǎng)站變得非常受歡迎氢惋,同時又很多請求進來洞翩。假設(shè)回調(diào)函數(shù)需執(zhí)行1秒。在第一個請求后緊跟著又來了第二個請求明肮,那么第二個請求將不會在這1秒內(nèi)被處理菱农。顯然1秒其實是很長的時間了。

讓我們來看看真實應用情景柿估,事件循環(huán)阻塞的問題會嚴重的破壞用戶體驗循未。HTTP服務(wù)器實際上是由操作系統(tǒng)內(nèi)核處理與客戶端的TCP連接的。所以盡管不會惡化到拒絕新連接的境地,但仍然會有這些鏈接不被處理的危險的妖。為了處理這些問題绣檬,希望盡量保持Node.js的事件驅(qū)動和非阻塞的特性。同樣的方式嫂粟,讓費時的I/O事件回調(diào)的方法來通知Node.js娇未,只有數(shù)據(jù)已經(jīng)準備好了,才可以進行下一步操作星虹。Node.js程序本身需要把每個回調(diào)函數(shù)都寫得運行迅速零抬,防止把事件循環(huán)給阻塞住。

這意味著編寫Node.js服務(wù)器程序的時候需遵循以下兩個策略:

  • 在設(shè)置完成以后所有的操作都是事件驅(qū)動的
  • 如果Node.js需長時間處理數(shù)據(jù)就要考慮把它分配給web worker去處理
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末宽涌,一起剝皮案震驚了整個濱河市平夜,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌卸亮,老刑警劉巖忽妒,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異兼贸,居然都是意外死亡段直,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進店門溶诞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來鸯檬,“玉大人,你說我怎么就攤上這事很澄【┤颍” “怎么了?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵甩苛,是天一觀的道長蹂楣。 經(jīng)常有香客問我,道長讯蒲,這世上最難降的妖魔是什么痊土? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮墨林,結(jié)果婚禮上赁酝,老公的妹妹穿的比我還像新娘。我一直安慰自己旭等,他們只是感情好酌呆,可當我...
    茶點故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著搔耕,像睡著了一般隙袁。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天菩收,我揣著相機與錄音梨睁,去河邊找鬼。 笑死娜饵,一個胖子當著我的面吹牛坡贺,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播箱舞,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼遍坟,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了褐缠?” 一聲冷哼從身側(cè)響起政鼠,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎队魏,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體万搔,經(jīng)...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡胡桨,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了瞬雹。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片昧谊。...
    茶點故事閱讀 40,133評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖酗捌,靈堂內(nèi)的尸體忽然破棺而出呢诬,到底是詐尸還是另有隱情,我是刑警寧澤胖缤,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布尚镰,位于F島的核電站,受9級特大地震影響哪廓,放射性物質(zhì)發(fā)生泄漏狗唉。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一涡真、第九天 我趴在偏房一處隱蔽的房頂上張望分俯。 院中可真熱鬧,春花似錦哆料、人聲如沸缸剪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽杏节。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間拢锹,已是汗流浹背谣妻。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留卒稳,地道東北人蹋半。 一個月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像充坑,于是被迫代替她去往敵國和親减江。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,077評論 2 355

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

  • Node.js 是單進程單線程應用程序,但是通過事件和回調(diào)支持并發(fā),所以性能非常高许帐。 Node.js 的每一個 A...
    Junting閱讀 331評論 0 2
  • 關(guān)鍵詞:Event Loop Node.js 使用事件驅(qū)動模型呢燥,當web server接收到請求,就把它關(guān)閉然后進...
    ferrint閱讀 292評論 0 1
  • node.js是單進程單線程應用程序励稳,通過事件和回調(diào)支持并發(fā)。node.js的沒一個API都是異步的,并作為一個獨...
    Amy莫莫閱讀 401評論 0 2
  • topics: 1.The Node.js philosophy 2.The reactor pattern 3....
    宮若石閱讀 1,084評論 0 1
  • Node.js是目前非辰嫡火熱的技術(shù),但是它的誕生經(jīng)歷卻很奇特囚霸。 眾所周知腰根,在Netscape設(shè)計出JavaScri...
    Myselfyan閱讀 4,072評論 2 58