如果我們平常有瀏覽有關Node.js的文章,估計我們都會聽到最多關于Node.js是異步非阻塞I/O,單線程悲酷,事件機制。本章節(jié)主要去深入探討這幾種特性(PS:本文是對所學知識的學習和總結,可能存在理解錯誤借嗽。請帶著懷疑的眼光琳轿,同時如果有錯誤希望能指出判沟。)
一、Node.js的異步I/O崭篡,非阻塞I/O
首先我們先來理解幾個概念:阻塞IO(blocking I/O)和非阻塞IO(non-blocking I/O)挪哄、同步IO(synchronous I/O)和異步IO(synchronous I/O)。
問題:這里肯定有人想問琉闪,異步I/O和非阻塞I/O不是一回事嗎迹炼??
答案:異步I/O和非阻塞I/O根本不是同一回事颠毙,曾經筆者一直天真的以為非阻塞I/O就是異步I/O T_T(直到看見樸靈大神的深入淺出Node.js)斯入。
1.現(xiàn)在我們來了解什么是異步I/O,什么是同步I/O蛀蜜?
這里轉自有趣的知乎er
老張愛喝茶刻两,廢話不說,煮開水涵防。出場人物:老張闹伪,水壺兩把(普通水壺,簡稱水壺壮池;會響的水壺偏瓤,簡稱響水壺)。
1:老張把水壺放到火上椰憋,立等水開厅克。(同步阻塞)老張覺得自己有點傻
2: 老張把水壺放到火上,去客廳看電視橙依,時不時去廚房看看水開沒有证舟。(同步非阻塞)老張還是覺得自己有點傻硕旗,于是變高端了,買了把會響笛的那種水壺女责。水開之后漆枚,能大聲發(fā)出嘀~~~~的噪音。
3:老張把響水壺放到火上抵知,立等水開墙基。(異步阻塞)老張覺得這樣傻等意義不大
4: 老張把響水壺放到火上,去客廳看電視刷喜,水壺響之前不再去看它了残制,響了再去拿壺。(異步非阻塞)老張覺得自己聰明了掖疮。
所謂同步異步初茶,只是對于水壺而言。普通水壺浊闪,同步恼布;響水壺,異步规揪。雖然都能干活桥氏,但響水壺可以在自己完工之后,提示老張水開了猛铅。這是普通水壺所不能及的字支。同步只能讓調用者去輪詢自己(情況2中),造成老張效率的低下奸忽。
所謂阻塞非阻塞堕伪,僅僅對于老張而言。立等的老張栗菜,阻塞欠雌;看電視的老張,非阻塞疙筹。情況1和情況3中老張就是阻塞的富俄,媳婦喊他都不知道。雖然3中響水壺是異步的而咆,可對于立等的老張沒有太大的意義霍比。所以一般異步是配合非阻塞使用的,這樣才能發(fā)揮異步的效用暴备。
作者:愚抄
鏈接:https://www.zhihu.com/question/19732473/answer/23434554
來源:知乎
著作權歸作者所有悠瞬。商業(yè)轉載請聯(lián)系作者獲得授權,非商業(yè)轉載請注明出處。
二浅妆、單線程
在專題一的介紹望迎,我們指導Node.js的runtime是V8,而V8設計是為了讓Chrome瀏覽器對Javascript語言進行編譯和解析的凌外,另外有過JS經驗的工程師都知道辩尊,JS的最大特點是單線程,而Node.js對V8的延用也是針對這一非常重要的特點趴乡。那么什么是單線程,單線程指的是对省,一個進程只能擁有一個線程,程序按順序執(zhí)行晾捏,只有等到前面的程序執(zhí)行完畢,才能執(zhí)行下一個!來看看Node對Http服務的模型哀托,
Node.js單線程指的是主線程是單線程惦辛,由主線程按照編碼順序一步步執(zhí)行,當主線程遇到阻塞的時候仓手,后續(xù)的程序就會被卡住無法執(zhí)行胖齐。說了這么多,不如來實踐一下驗真假:
先將index.js的代碼改成這樣嗽冒,然后打開瀏覽器呀伙,你會發(fā)現(xiàn)瀏覽器在10秒之后才做出反應,打出Hello Node.js
JavaScript是解析性語言添坊,代碼按照編碼順序一行一行被壓進stack里面執(zhí)行剿另,執(zhí)行完成后移除然后繼續(xù)壓下一行代碼塊進去執(zhí)行。上面代碼塊的堆棧圖贬蛙,當主線程接受了request后雨女,程序被壓進同步執(zhí)行的sleep執(zhí)行塊(我們假設這里就是程序的業(yè)務處理),如果在這10s內有第二個request進來就會被壓進stack里面等待10s執(zhí)行完成后再進一步處理下一個請求阳准,后面的請求都會被掛起等待前面的同步執(zhí)行完成后再執(zhí)行氛堕,所以這也說明Node.js單線程的執(zhí)行模型,因為這樣的特性野蝇,我們的頁面不能有耗時很長的同步處理程序阻塞了程序的后續(xù)執(zhí)行讼稚,而對于耗時過長的程序應該采用異步執(zhí)行,這里也就是Node.js的第二個特性绕沈,異步锐想。
三、異步
我們平時說的Node.js是異步的七冲,那么具體是指那部分異步,而答案是主線程的異步處理函數(shù)隊列+多線程異步I/O
1.主線程的異步處理函數(shù)隊列
上面那句話看上去是不是有點抽象難懂痛倚,現(xiàn)在來進行解釋,所謂主線程異步處理函數(shù)隊列是指主線程的主要執(zhí)行空間除了stack(執(zhí)行棧)和head(產生堆)外澜躺,還會callback queue(回調函數(shù)隊列),而callback queue是存放了異步處理的回調函數(shù)蝉稳,當一個異步I/O處理完抒蚜,就會向callback queue存放一個回調函數(shù),當stack里面的程序執(zhí)行完耘戚,主線程就會從callback queue取出已經存放好的回調函數(shù)去執(zhí)行嗡髓,而我們平時最常見的異步,除了事件外收津,還有timer饿这,例如setTimeout,不如我們舉一個栗子~
let sleep = (time)=>{
let exit = Date.now+time*1000;
while(Date.now()<exit){};
console.log('end sleep')
return
}
let main = ()=>{
setTimeout(()=>{
console.log('setTimeout run');
},0)
sleep(5);
console.log('after sleep');
}
main()
執(zhí)行輸出:
end sleep
after sleep
undefined(由于每個函數(shù)執(zhí)行完都會自行return撞秋,如果沒指定长捧,就會輸出undefined,這個是main執(zhí)行完return出來的)
setTimeout run
下面是代碼塊的主線程堆棧執(zhí)行:
看上圖吻贿,主線程將main函數(shù)壓進stack里面一行行解析執(zhí)行串结,首先遇到setTimeout方法,因為setTimeout是一個異步處理函數(shù)舅列,這里會setTimeout(callback,timeout)肌割,里面的callback函數(shù)移進callback queue里面,同時會把自己從主線程的stack里面移除帐要,繼續(xù)壓進后面的執(zhí)行代碼來解析執(zhí)行把敞,這里繼續(xù)壓進sleep沉睡5s,接下來執(zhí)行console榨惠,等到這里的同步代碼執(zhí)行完成后這個時候就會從callback queue里面取回調函數(shù)一個個執(zhí)行亡哄。(題外話:就算setTimeout里面的timeout設置了是0政鼠,都是要等待執(zhí)行塊里面的同步代碼執(zhí)行完成后再去執(zhí)行callback queue里面的代碼)這就是異步里面的其一:主線程異步函數(shù)處理隊列。(PS,setTimeout的回調函數(shù)的執(zhí)行時間不是當前隊列,而是下一個執(zhí)行隊列狡耻,即是是設置為偶洋,也最多是下一次執(zhí)行隊列第一個執(zhí)行版保,詳情阮一峰JS運行機制)
2.多線程異步I/O
這里可能有人會有疑問洲愤,買賣皮喔!你不是說Node.js是單線程嗎横侦,你這不是自己打臉嗎?我想說挥萌,這里其實是沒有沖突的,Node.js每個進程里面只有一個主線程來處理程序枉侧。因此引瀑,主線程是單線程,而主線程之外調用的I/O處理是通過一個叫做線程池的結構來管理的榨馁,所以I/O的處理是多線程的憨栽,而主線程和I/O線程池則通過上面剛剛講述的主線程的異步處理函數(shù)隊列來協(xié)作。(PSNode.js只對文件系統(tǒng)以及DNS實現(xiàn)了多線程I/O封裝,網絡I/O還是采用單線程形式)如圖:
上圖在主線程中屑柔,當遇到需要處理的I/O時屡萤,就將I/O的處理放在I/O線程池中管理,而主線程繼續(xù)執(zhí)行掸宛,當I/O線程池中有I/O完成了死陆,就會想callback queue注冊回調函數(shù)等待主線程執(zhí)行,而Node.js的高性能也是得益于其將阻塞的I/O異步化唧瘾,使得不影響主邏輯的執(zhí)行措译。
四、事件驅動
文章至此饰序,我們先進行總結领虹,Node.js至此我們簡介了兩個主要特性,單線程菌羽,異步,每個Node程序只會在主線程中執(zhí)行程序代碼掠械,在執(zhí)行過程中將阻塞的I/O操作異步化,將放至I/O線程池中進行管理注祖,當線程池中有I/O操作完成,就會向callback queue注冊回調函數(shù)均唉,等待同步邏輯執(zhí)行完成后再通過callback queue里面取出回調函數(shù)壓進stack里面執(zhí)行是晨,好了,而事件驅動的作用就是取出回調函數(shù)舔箭。事件驅動又叫事件循環(huán)罩缴,是指主線程從主線程的異步處理函數(shù)隊列里面不停循環(huán)的讀取事件,驅動了所有的異步回調函數(shù)的執(zhí)行层扶。詳情Node事件輪詢
至此整個Node.js的異步化邏輯可以不斷循環(huán)的跑起來了箫章,以上則是我們日常所言的Node.js的三大特性以及其原理。