摘要:異步和事件驅(qū)動(dòng)注本文是對(duì)眾多博客的學(xué)習(xí)和總結(jié),可能存在理解錯(cuò)誤。接觸有兩個(gè)月,對(duì)的兩大特性一直有點(diǎn)模糊,即異步和事件驅(qū)動(dòng)。
nodejs 異步I/O和事件驅(qū)動(dòng)
注:本文是對(duì)眾多博客的學(xué)習(xí)和總結(jié),可能存在理解錯(cuò)誤。請(qǐng)帶著懷疑的眼光,同時(shí)如果有錯(cuò)誤希望能指出。
接觸nodejs有兩個(gè)月,對(duì)nodejs的兩大特性一直有點(diǎn)模糊,即異步IO和事件驅(qū)動(dòng)。通過對(duì)《深入淺出nodejs》和幾篇博客的閱讀以后,有了大致的了解,總結(jié)一下。
幾個(gè)例子在開始之前,先來看幾個(gè)簡(jiǎn)單例子,這也是我在使用nodejs時(shí)候遇到的幾個(gè)比較困惑的例子。
example 1var fs = require("fs"); var debug = require("debug")("example1"); debug("begin"); setTimeout(function(){ debug("timeout1"); }); setTimeout(function(){ debug("timeout2"); }); debug("end"); /** 運(yùn)行結(jié)果 Sat, 21 May 2016 08:41:09 GMT example1 begin Sat, 21 May 2016 08:41:09 GMT example1 end Sat, 21 May 2016 08:41:09 GMT example1 timeout1 Sat, 21 May 2016 08:41:09 GMT example1 timeout2 */
question 1
example 2為何timeout1和timeout2的結(jié)果會(huì)在end后面?
var fs = require("fs"); var debug = require("debug")("example2"); debug("begin"); setTimeout(function(){ debug("timeout1"); }); setTimeout(function(){ debug("timeout2"); }); debug("end"); while(true); /** 運(yùn)行結(jié)果 Sat, 21 May 2016 08:45:47 GMT example2 begin Sat, 21 May 2016 08:45:47 GMT example2 end */
question 2
example 3為何timeout1和timeout2沒有輸出到終端?while(true)到底阻塞了什么?
var fs = require("fs"); var debug = require("debug")("example3"); debug("begin"); setTimeout(function(){ debug("timeout1"); while (true); }); setTimeout(function(){ debug("timeout2"); }); debug("end"); /** 運(yùn)行結(jié)果 Sat, 21 May 2016 08:49:12 GMT example3 begin Sat, 21 May 2016 08:49:12 GMT example3 end Sat, 21 May 2016 08:49:12 GMT example3 timeout1 */
question 3
example 4為什么timeout1中回調(diào)函數(shù)會(huì)阻塞timeout2中的回調(diào)函數(shù)的執(zhí)行?
var fs = require("fs"); var debug = require("debug")("example4"); debug("begin"); setTimeout(function(){ debug("timeout1"); /** * 模擬計(jì)算密集 */ for(var i = 0 ; i < 1000000 ; ++i){ for(var j = 0 ; j < 100000 ; ++j); } }); setTimeout(function(){ debug("timeout2"); }); debug("end"); /** Sat, 21 May 2016 08:53:27 GMT example4 begin Sat, 21 May 2016 08:53:27 GMT example4 end Sat, 21 May 2016 08:53:27 GMT example4 timeout1 Sat, 21 May 2016 08:54:09 GMT example4 timeout2 //注意這里的時(shí)間晚了好久 */
question 4
example 5和上面的問題一樣,為何timeout1的計(jì)算密集型工作將會(huì)阻塞timeout2的回調(diào)函數(shù)的執(zhí)行?
var fs = require("fs"); var debug = require("debug")("example5"); debug("begin"); fs.readFile("package.json","utf-8",function(err,data){ if(err) debug(err); else debug("get file content"); }); setTimeout(function(){ debug("timeout2"); }); debug("end"); /** 運(yùn)行結(jié)果 Sat, 21 May 2016 08:59:14 GMT example5 begin Sat, 21 May 2016 08:59:14 GMT example5 end Sat, 21 May 2016 08:59:14 GMT example5 timeout2 Sat, 21 May 2016 08:59:14 GMT example5 get file content */
question 5
為何讀取文件的IO操作不會(huì)阻塞timeout2的執(zhí)行?
接下來我們就帶著上面幾個(gè)疑惑去理解nodejs中的異步IO和事件驅(qū)動(dòng)是如何工作的。
異步IO(asynchronous I/O)首先來理解幾個(gè)容易混淆的概念,阻塞IO(blocking I/O)和非阻塞IO(non-blocking I/O),同步IO(synchronous I/O)和異步IO(synchronous I/O)。
博主一直天真的以為非阻塞I/O就是異步I/O T_T,apue一直沒有讀懂。
阻塞I/O 和 非阻塞I/O簡(jiǎn)單來說,阻塞I/O就是當(dāng)用戶發(fā)一個(gè)讀取文件描述符的操作的時(shí)候,進(jìn)程就會(huì)被阻塞,直到要讀取的數(shù)據(jù)全部準(zhǔn)備好返回給用戶,這時(shí)候進(jìn)程才會(huì)解除block的狀態(tài)。
那非阻塞I/O呢,就與上面的情況相反,用戶發(fā)起一個(gè)讀取文件描述符操作的時(shí),函數(shù)立即返回,不作任何等待,進(jìn)程繼續(xù)執(zhí)行。但是程序如何知道要讀取的數(shù)據(jù)已經(jīng)準(zhǔn)備好了呢?最簡(jiǎn)單的方法就是輪詢。
除此之外,還有一種叫做IO多路復(fù)用的模式,就是用一個(gè)阻塞函數(shù)同時(shí)監(jiān)聽多個(gè)文件描述符,當(dāng)其中有一個(gè)文件描述符準(zhǔn)備好了,就馬上返回,在linux下,select,poll,epoll都提供了IO多路復(fù)用的功能。
同步I/O 和 異步I/O那么同步I/O和異步I/O又有什么區(qū)別么?是不是只要做到非阻塞IO就可以實(shí)現(xiàn)異步I/O呢?
其實(shí)不然。
同步I/O(synchronous I/O)做I/O operation的時(shí)候會(huì)將process阻塞,所以阻塞I/O,非阻塞I/O,IO多路復(fù)用I/O都是同步I/O。
異步I/O(asynchronous I/O)做I/O opertaion的時(shí)候?qū)⒉粫?huì)造成任何的阻塞。
非阻塞I/O都不阻塞了為什么不是異步I/O呢?其實(shí)當(dāng)非阻塞I/O準(zhǔn)備好數(shù)據(jù)以后還是要阻塞住進(jìn)程去內(nèi)核拿數(shù)據(jù)的。所以算不上異步I/O。
這里借一張圖(圖來自這里)來說明他們之間的區(qū)別
][1]
更多IO更多的詳細(xì)內(nèi)容可以在這里找到:
Linux IO模式及 select、poll、epoll詳解
select / poll / epoll: practical difference for system architects
事件驅(qū)動(dòng)事件驅(qū)動(dòng)(event-driven)是nodejs中的第二大特性。何為事件驅(qū)動(dòng)呢?簡(jiǎn)單來說,就是通過監(jiān)聽事件的狀態(tài)變化來做出相應(yīng)的操作。比如讀取一個(gè)文件,文件讀取完畢,或者文件讀取錯(cuò)誤,那么就觸發(fā)對(duì)應(yīng)的狀態(tài),然后調(diào)用對(duì)應(yīng)的回掉函數(shù)來進(jìn)行處理。
線程驅(qū)動(dòng)和事件驅(qū)動(dòng)那么線程驅(qū)動(dòng)編程和事件驅(qū)動(dòng)編程之間的區(qū)別是什么呢?
線程驅(qū)動(dòng)就是當(dāng)收到一個(gè)請(qǐng)求的時(shí)候,將會(huì)為該請(qǐng)求開一個(gè)新的線程來處理請(qǐng)求。一般存在一個(gè)線程池,線程池中有空閑的線程,會(huì)從線程池中拿取線程來進(jìn)行處理,如果線程池中沒有空閑的線程,新來的請(qǐng)求將會(huì)進(jìn)入隊(duì)列排隊(duì),直到線程池中空閑線程。
事件驅(qū)動(dòng)就是當(dāng)進(jìn)來一個(gè)新的請(qǐng)求的時(shí),請(qǐng)求將會(huì)被壓入隊(duì)列中,然后通過一個(gè)循環(huán)來檢測(cè)隊(duì)列中的事件狀態(tài)變化,如果檢測(cè)到有狀態(tài)變化的事件,那么就執(zhí)行該事件對(duì)應(yīng)的處理代碼,一般都是回調(diào)函數(shù)。
對(duì)于事件驅(qū)動(dòng)編程來說,如果某個(gè)時(shí)間的回調(diào)函數(shù)是計(jì)算密集型,或者是阻塞I/O,那么這個(gè)回調(diào)函數(shù)將會(huì)阻塞后面所有事件回調(diào)函數(shù)的執(zhí)行。這一點(diǎn)尤為重要。
nodejs的事件驅(qū)動(dòng)和異步I/O 事件驅(qū)動(dòng)模型上面介紹了那么多的概念,現(xiàn)在我們來看看nodejs中的事件驅(qū)動(dòng)和異步I/O是如何實(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ì)列中。
nodejs的時(shí)間驅(qū)動(dòng)模型一般要注意下面幾個(gè)點(diǎn):
因?yàn)槭?strong>單線程的,所以當(dāng)順序執(zhí)行js文件中的代碼的時(shí)候,事件循環(huán)是被暫停的。
當(dāng)js文件執(zhí)行完以后,事件循環(huán)開始運(yùn)行,并從消息隊(duì)列中取出消息,開始執(zhí)行回調(diào)函數(shù)
因?yàn)槭?strong>單線程的,所以當(dāng)回調(diào)函數(shù)被執(zhí)行的時(shí)候,事件循環(huán)是被暫停的
當(dāng)涉及到I/O操作的時(shí)候,nodejs會(huì)開一個(gè)獨(dú)立的線程來進(jìn)行異步I/O操作,操作結(jié)束以后將消息壓入消息隊(duì)列。
下面我們從一個(gè)簡(jiǎn)單的js文件入手,來看看 nodejs是如何執(zhí)行的。
var fs = require("fs"); var debug = require("debug")("example1"); debug("begin"); fs.readFile("package.json","utf-8",function(err,data){ if(err) debug(err); else debug("get file content"); }); setTimeout(function(){ debug("timeout2"); }); debug("end"); // 運(yùn)行到這里之前,事件循環(huán)是暫停的
同步執(zhí)行debug("begin")
異步調(diào)用fs.readFile(),此時(shí)會(huì)開一個(gè)新的線程去進(jìn)行異步I/O操作
異步調(diào)用setTimeout(),馬上將超時(shí)信息壓入到消息隊(duì)列中
同步調(diào)用debug("end")
開啟事件循環(huán),彈出消息隊(duì)列中的信息(目前是超時(shí)信息)
然后執(zhí)行信息對(duì)應(yīng)的回調(diào)函數(shù)(事件循環(huán)又被暫停)
回調(diào)函數(shù)執(zhí)行結(jié)束后,開始事件循環(huán)(目前消息隊(duì)列中沒有任何東西,文件還沒讀完)
異步I/O讀取文件完畢,將消息壓入消息隊(duì)列(消息中含有文件內(nèi)容或者是出錯(cuò)信息)
事件循環(huán)取得消息,執(zhí)行回調(diào)
程序退出。
這里借一張圖來說明nodejs的事件驅(qū)動(dòng)模型(圖來自這里)
][2]
這里最后要說的一點(diǎn)就是如何手動(dòng)將一個(gè)函數(shù)推入隊(duì)列,nodejs為我們提供了幾個(gè)比較方便的方法:
setTimeout()
process.nextTick()
setImmediate()
異步I/Onodejs中的異步I/O的操作是通過libuv這個(gè)庫(kù)來實(shí)現(xiàn)的,包含了window和linux下面的異步I/O實(shí)現(xiàn),博主也沒有研究過這個(gè)庫(kù),感興趣的讀者可以移步到這里
問題答案好,到目前為止,已經(jīng)可以回答上面的問題了
question 1
為何timeout1和timeout2的結(jié)果會(huì)在end后面?
answer 1
因?yàn)榇藭r(shí)timeout1和timeout2只是被異步函數(shù)推入到了隊(duì)列中,事件循環(huán)還是暫停狀態(tài)
question 2
為何timeout1和timeout2沒有輸出到終端?while(true)到底阻塞了什么?
answer 2
因?yàn)榇颂幹苯幼枞?strong>事件循環(huán),還沒開始,就已經(jīng)被阻塞了
question 3,4
為什么timeout1中回調(diào)函數(shù)會(huì)阻塞timeout2中的回調(diào)函數(shù)的執(zhí)行?
為何timeout1的計(jì)算密集型工作將會(huì)阻塞timeout2的回調(diào)函數(shù)的執(zhí)行?
answer 3,4
因?yàn)樵摶卣{(diào)函數(shù)執(zhí)行返回事件循環(huán)才會(huì)繼續(xù)執(zhí)行,回調(diào)函數(shù)將會(huì)阻塞事件循環(huán)的運(yùn)行
question 5
為何讀取文件的IO操作不會(huì)阻塞timeout2的執(zhí)行?
answer 5
因?yàn)?b>IO操作是異步的,會(huì)開啟一個(gè)新的線程,不會(huì)阻塞到事件循環(huán)
參考文獻(xiàn):
What exactly is a Node.js event loop tick?
What is the difference between a thread-based server and an event-based server?
Some confusion about nodejs threads
The JavaScript Event Loop: Explained
poll vs select vs event-based
Linux IO模式及 select、poll、epoll詳解
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/79517.html
摘要:使用了一個(gè)事件驅(qū)動(dòng)非阻塞式的模型,使其輕量又高效。的包管理器,是全球最大的開源庫(kù)生態(tài)系統(tǒng)。按照這個(gè)定義,之前所述的阻塞,非阻塞,多路復(fù)用信號(hào)驅(qū)動(dòng)都屬于同步。 系列文章 Nodejs高性能原理(上) --- 異步非阻塞事件驅(qū)動(dòng)模型Nodejs高性能原理(下) --- 事件循環(huán)詳解 前言 終于開始我nodejs的博客生涯了,先從基本的原理講起.以前寫過一篇瀏覽器執(zhí)行機(jī)制的文章,和nodej...
摘要:事件驅(qū)動(dòng)在中,當(dāng)某個(gè)執(zhí)行完畢后,會(huì)以事件的形式通知執(zhí)行操作的線程而線程去執(zhí)行對(duì)應(yīng)事件的回調(diào)函數(shù)。為了處理異步,線程必須要有事件循環(huán),不斷的檢查有沒有事件要處理,并依次處理。其實(shí)在底層中,有一半的代碼,都是在處理事件隊(duì)列回調(diào)函數(shù)。 事件驅(qū)動(dòng) 上一節(jié)中,我們提到異步I/O;當(dāng)I/O處理完畢后,nodejs是怎樣知道I/O已經(jīng)完成了呢?又是怎樣去處理的呢?答案是:事件驅(qū)動(dòng)(事件循環(huán))機(jī)制。 ...
原文 先說1.1總攬: Reactor模式 Reactor模式中的協(xié)調(diào)機(jī)制Event Loop Reactor模式中的事件分離器Event Demultiplexer 一些Event Demultiplexer處理不了的復(fù)雜I/O接口比如File I/O、DNS等 復(fù)雜I/O的解決方案 未完待續(xù) 前言 nodejs和其他編程平臺(tái)的區(qū)別在于如何去處理I/O接口,我們聽一個(gè)人介紹nodejs,總是...
摘要:瀏覽器拿到了簡(jiǎn)書網(wǎng)的完整的頁(yè)面代碼,在解析和渲染這個(gè)頁(yè)面的時(shí)候,里面的圖片靜態(tài)資源,他們同樣也是一個(gè)個(gè)請(qǐng)求都需要經(jīng)過上面的主要的七個(gè)步驟。瀏覽器根據(jù)拿到的資源對(duì)頁(yè)面進(jìn)行渲染,最終把一個(gè)完整的頁(yè)面呈現(xiàn)給了用戶。 瀏覽器訪問一個(gè)網(wǎng)站所經(jīng)歷的步驟 Chrome搜索自身的DNS緩存 搜索操作系統(tǒng)自身的DNS緩存(瀏覽器沒有找到緩存或緩存已經(jīng)失效)查看Chrome瀏覽器的DNS緩存信息(chr...
摘要:什么是在中什么時(shí)候需要是中的包管理器。允許我們?yōu)榘惭b各種模塊,這個(gè)包管理器為我們提供了安裝刪除等其它命令來管理模塊。 showImg(https://user-gold-cdn.xitu.io/2019/7/11/16bde5b2df52a924?w=4000&h=2667&f=jpeg&s=450648); 本文為您分享「Node.js 入門你需要知道的 10 個(gè)問題」這些問題可能也...
閱讀 2754·2021-09-24 09:47
閱讀 4378·2021-08-27 13:10
閱讀 3027·2019-08-30 15:44
閱讀 1292·2019-08-29 12:56
閱讀 2600·2019-08-28 18:07
閱讀 2622·2019-08-26 14:05
閱讀 2577·2019-08-26 13:41
閱讀 1272·2019-08-26 13:33