title: Node.js中的異步
categories: tech
tags:
- 異步
- Node.js
前言
寫Node.js也有一段時(shí)間了,但一直沒有理解Node.Js中的異步阻桅,同時(shí)我也有了更多的疑問異步回調(diào)和同步回調(diào)的區(qū)別,Node.js采用異步的優(yōu)勢(shì)是什么?如何才能優(yōu)雅的寫Node.js的代碼?
Node.js的單線程
先來看下Node.js的結(jié)構(gòu),分為三層莱褒,如下:
Node.js 標(biāo)準(zhǔn)庫(kù),這部分是由 Javascript 編寫的涎劈,即我們使用過程中直接能調(diào)用的 API广凸。在源碼中的 lib 目錄下可以看到。
Node bindings蛛枚,這一層是 Javascript 與底層 C/C++ 能夠溝通的關(guān)鍵谅海,前者通過 bindings 調(diào)用后者,相互交換數(shù)據(jù)蹦浦。實(shí)現(xiàn)在 node.cc
-
這一層是支撐 Node.js 運(yùn)行的關(guān)鍵扭吁,由 C/C++ 實(shí)現(xiàn)。
- V8:Google 推出的 Javascript VM,也是 Node.js 為什么使用的是 Javascript 的關(guān)鍵侥袜,它為 Javascript 提供了在非瀏覽器端運(yùn)行的環(huán)境蝌诡,它的高效是 Node.js 之所以高效的原因之一。
- Libuv:它為 Node.js 提供了跨平臺(tái)枫吧,線程池送漠,事件池,異步 I/O 等能力由蘑,是 Node.js 如此強(qiáng)大的關(guān)鍵闽寡。
- C-ares:提供了異步處理 DNS 相關(guān)的能力。
- http_parser尼酿、OpenSSL爷狈、zlib 等:提供包括 http 解析、SSL裳擎、數(shù)據(jù)壓縮等其他的能力涎永。
Node.js 雖然說是用的 Javascript,但只是在開發(fā)時(shí)使用 Javascript 的語(yǔ)法來編寫程序鹿响。真正的執(zhí)行過程還是由 V8 將 Javascript 解釋羡微,然后由 C/C++ 來執(zhí)行真正的系統(tǒng)調(diào)用。
真正執(zhí)行系統(tǒng)調(diào)用的其實(shí)是 Libuv惶我。之前我們提到妈倔,Libuv 本身就是異步和事件驅(qū)動(dòng)的,所以绸贡,當(dāng)我們將 I/O 操作的請(qǐng)求傳達(dá)給 Libuv 之后盯蝴,Libuv 開啟線程來執(zhí)行這次 I/O 調(diào)用,并在執(zhí)行完成后听怕,傳回給 Javascript 進(jìn)行后續(xù)處理捧挺。
這里面的 I/O 包括文件 I/O 和 網(wǎng)絡(luò) I/O。兩者的底層執(zhí)行略有不同尿瞭。從上面的 Libuv 官網(wǎng)的圖中闽烙,我們可以看到,文件 I/O声搁,DNS 等操作黑竞,都是依托線程池(Thread Pool)來實(shí)現(xiàn)的。而網(wǎng)絡(luò) I/O 這一大類酥艳,包括:TCP摊溶、UDP、TTY 等充石,是由 epoll莫换、IOCP、kqueue 來具體實(shí)現(xiàn)的。
它的單線程指的是自身 Javascript 運(yùn)行環(huán)境的單線程拉岁,Node.js 并沒有給 Javascript 執(zhí)行時(shí)創(chuàng)建新線程的能力坷剧,最終的實(shí)際操作,還是通過 Libuv 以及它的事件循環(huán)來執(zhí)行的喊暖。
異步和同步
異步是對(duì)進(jìn)程在和I/O(有文件I/O惫企、網(wǎng)絡(luò)I/O)交互而言的。在做網(wǎng)絡(luò)請(qǐng)求時(shí)候陵叽,假設(shè)這個(gè)操作需要2s狞尔,同步會(huì)等待這個(gè)2s的請(qǐng)求完成再去做下一個(gè)動(dòng)作;然后異步會(huì)直接進(jìn)行下一個(gè)動(dòng)作巩掺。
下面這段代碼偏序,輸出的是null,因?yàn)榫W(wǎng)絡(luò)I/O這個(gè)請(qǐng)求是異步的胖替,執(zhí)行到異步相關(guān)操作的時(shí)候會(huì)立即返回研儒。
var statusCode = null;
request('https://www.baidu.com/', function(error, response, body) {
var statusCode = response.statusCode;
});
console.log(statusCode);
為什么Node.js要設(shè)計(jì)為異步
目前為止我接觸的編程語(yǔ)言都是同步,剛開始寫 Node.js 那是相當(dāng)?shù)牟涣?xí)慣独令,即使現(xiàn)在習(xí)慣了還是覺得這樣編程很麻煩端朵。既然如此那為何要把 Node.js 設(shè)計(jì)成異步的呢?
異步并不會(huì)少程序的運(yùn)行時(shí)間燃箭,不會(huì)因?yàn)槟阌昧水惒匠迥兀樵償?shù)據(jù)庫(kù)的時(shí)間就會(huì)從10ms減少到5ms,無論是同步還是異步遍膜,執(zhí)行的時(shí)間依然是10ms碗硬,異步的作用僅僅是提升了應(yīng)用程序的吞吐量瓤湘。進(jìn)一步說瓢颅,異步 IO 會(huì)減少單個(gè)請(qǐng)求的時(shí)間,去掉單個(gè)請(qǐng)求中那些無意義的等待時(shí)間(從用戶角度來看)弛说。所以單位時(shí)間內(nèi)處理的請(qǐng)求沒有變化(從服務(wù)器監(jiān)督看)挽懦,但每個(gè)請(qǐng)求的處理時(shí)間卻減少了。從這個(gè)角度木人,服務(wù)器也節(jié)約了一些資源——即維持每個(gè)請(qǐng)求的連接消耗的內(nèi)存信柿。
異步這么麻煩應(yīng)該怎么寫
最簡(jiǎn)單的實(shí)現(xiàn)異步的方法就是回調(diào),但是對(duì)于有時(shí)序性的醒第,異步多了之后就成了回調(diào)陷阱渔嚷,代碼會(huì)像下面這個(gè)樣子,瞬間感覺自己智商跟不上了要:
request('https://www.baidu.com/', function(error, response, body) {
var statusCode = response.statusCode;
request('https://www.baidu.com/', function(error, response, body) {
var statusCode = response.statusCode;
request('https://www.baidu.com/', function(error, response, body) {
var statusCode = response.statusCode;
});
});
});
當(dāng)然現(xiàn)在有很多解決方案稠曼,如下圖所示:
Node.js如何實(shí)現(xiàn)的異步
nodejs是單線程(single thread)運(yùn)行的形病,通過一個(gè)事件循環(huán)(event-loop)來循環(huán)取出消息隊(duì)列(event-queue)中的消息進(jìn)行處理,處理過程基本上就是去調(diào)用該消息對(duì)應(yīng)的回調(diào)函數(shù)。消息隊(duì)列就是當(dāng)一個(gè)事件狀態(tài)發(fā)生變化時(shí),就將一個(gè)消息壓入隊(duì)列中漠吻。
實(shí)際上量瓜,主線程只會(huì)做一件事情,就是從消息隊(duì)列里面取消息途乃、執(zhí)行消息绍傲,再取消息、再執(zhí)行耍共。當(dāng)消息隊(duì)列為空時(shí)烫饼,就會(huì)等待直到消息隊(duì)列變成非空。而且主線程只有在將當(dāng)前的消息執(zhí)行完成后试读,才會(huì)去取下一個(gè)消息枫弟。這種機(jī)制就叫做事件循環(huán)機(jī)制,取一個(gè)消息并執(zhí)行的過程叫做一次循環(huán)鹏往。
用ajax請(qǐng)求做一個(gè)具體說明:
--EOF--