欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

淺談Node 異步IO和事件循環(huán)

 更新時間:2019年05月05日 10:13:15   作者:菜的黑人牙膏  
這篇文章主要介紹了淺談Node 異步IO和事件循環(huán),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧

前言

學(xué)習(xí)Node就繞不開異步IO, 異步IO又與事件循環(huán)息息相關(guān), 而關(guān)于這一塊一直沒有仔細(xì)去了解整理過, 剛好最近在做項(xiàng)目的時候, 有了一些思考就記錄了下來, 希望能盡量將這一塊的知識整理清楚, 如有錯誤, 請指點(diǎn)輕噴~~

一些概念

 同步異步 & 阻塞非阻塞

查閱資料的時候, 發(fā)現(xiàn)很多人都對 異步和非阻塞 的概念有點(diǎn)混淆, 其實(shí)兩者是完全不同的, 同步異步指的是 行為即兩者之間的關(guān)系 , 而阻塞非阻塞指的是 狀態(tài)即某一方

以前端請求為一個例子,下面的代碼很多人都應(yīng)該寫過

$.ajax(url).succedd(() => {
 ......
 // to do something
})

同步異步

如果是同步的話, 那么應(yīng)該是client發(fā)起請求后, 一直等到serve處理請求完成后才返回繼續(xù)執(zhí)行后續(xù)的邏輯, 這樣 client和serve之間就保持了同步的狀態(tài) 。

如果是異步的話, 那么應(yīng)該是client發(fā)起請求后, 立即返回 , 而請求可能還沒有到達(dá)server端或者請求正在處理, 當(dāng)然在異步情況下, client端通常會注冊事件來處理請求完成后的情況, 如上面的succeed函數(shù)。

阻塞非阻塞

首先需要明白一個概念, Js是單線程, 但是瀏覽器并不是, 事實(shí)上你的請求是瀏覽器的另一個線程在跑。

如果是阻塞的話, 那么 該線程就會一直等到這個請求完成之后才能被釋放用于其他請求 。

如果是非阻塞的話, 那么 該線程就可以發(fā)起請求后而不用等請求完成繼續(xù)做其他事情

總結(jié)

之所以經(jīng)常會混亂是因?yàn)闆]有說清楚討論的是哪一部分(下面會提到), 所以 同步異步討論的對象是雙方, 而阻塞非阻塞討論的對象是自身

IO和CPU

Io和Cpu是可以同時進(jìn)行工作的 。

IO:

I/O(英語:Input/Output),即輸入/輸出,通常指數(shù)據(jù)在內(nèi)部存儲器和外部存儲器或其他周邊設(shè)備之間的輸入和輸出。

cpu

解釋計(jì)算機(jī)指令以及處理計(jì)算機(jī)軟件中的數(shù)據(jù)。

Node中的異步IO模型

IO分為 磁盤IO和網(wǎng)絡(luò)IO , 其具有兩個步驟

  1. 等待數(shù)據(jù)準(zhǔn)備 (Waiting for the data to be ready)
  2. 將數(shù)據(jù)從內(nèi)核拷貝到進(jìn)程中 (Copying the data from the kernel to the process)

Node中的磁盤Io

以下的討論基于*nix系統(tǒng)。

理想的異步Io應(yīng)該像上面討論的一樣, 如圖:

而實(shí)際上, 我們的系統(tǒng)并不能完美的實(shí)現(xiàn)這樣的一種調(diào)用方式, Node的異步IO, 如讀取文件等采用的是線程池的方式來實(shí)現(xiàn), 可以看到, Node通過另外一個線程來進(jìn)行Io操作, 完成后再通知主線程:

而在window下, 則是利用 IOCP 接口來完成, IOCP從用戶的角度來說確實(shí)是完美的異步調(diào)用方式, 而實(shí)際也是利用內(nèi)核中的線程池, 其與nix系統(tǒng)的不同在于后者的線程池是用戶層提供的線程池。

Node中的網(wǎng)絡(luò)Io

在進(jìn)入主題之前, 我們先了解下Linux的Io模式, 這里推薦大家看這篇文章, 大致總結(jié)如下:

阻塞 I/O(blocking IO)

所以,blocking IO的特點(diǎn)就是在IO執(zhí)行的兩個階段都被block了。

非阻塞 I/O(nonblocking IO)

當(dāng)用戶進(jìn)程發(fā)出read操作時,如果kernel中的數(shù)據(jù)還沒有準(zhǔn)備好,那么它并不會block用戶進(jìn)程,而是立刻返回一個error。從用戶進(jìn)程角度講 ,它發(fā)起一個read操作后,并不需要等待,而是馬上就得到了一個結(jié)果。用戶進(jìn)程判斷結(jié)果是一個error時,它就知道數(shù)據(jù)還沒有準(zhǔn)備好,于是它可以再次發(fā)送read操作。一旦kernel中的數(shù)據(jù)準(zhǔn)備好了,并且又再次收到了用戶進(jìn)程的system call,那么它馬上就將數(shù)據(jù)拷貝到了用戶內(nèi)存,然后返回。

I/O 多路復(fù)用( IO multiplexing)

所以,I/O 多路復(fù)用的特點(diǎn)是通過一種機(jī)制一個進(jìn)程能同時等待多個文件描述符,而這些文件描述符(套接字描述符)其中的任意一個進(jìn)入讀就緒狀態(tài),select()函數(shù)就可以返回。

異步 I/O(asynchronous IO)

用戶進(jìn)程發(fā)起read操作之后,立刻就可以開始去做其它的事。而另一方面,從kernel的角度,當(dāng)它受到一個asynchronous read之后,首先它會立刻返回,所以不會對用戶進(jìn)程產(chǎn)生任何block。然后,kernel會等待數(shù)據(jù)準(zhǔn)備完成,然后將數(shù)據(jù)拷貝到用戶內(nèi)存,當(dāng)這一切都完成之后,kernel會給用戶進(jìn)程發(fā)送一個signal,告訴它read操作完成了。

而在Node中, 采用的是I/O 多路復(fù)用的模式, 而在I/O多路復(fù)用的模式中, 又具有read, select, poll, epoll等幾個子模式, Node采用的是最優(yōu)的epoll模式, 這里簡單說下其中的區(qū)別, 并且解釋下為什么epoll是最優(yōu)的。

read

read。它是一種最原始、性能最低的一種,它會重復(fù)檢查I/O的狀態(tài)來完成數(shù)據(jù)的完整讀取。在得到最終數(shù)據(jù)前,CPU一直耗用在I/O狀態(tài)的重復(fù)檢查上。圖1是通過read進(jìn)行輪詢的示意圖。

select

select。它是在read的基礎(chǔ)上改進(jìn)的一種方案,通過對文件描述符上的事件狀態(tài)進(jìn)行判斷。圖2是通過select進(jìn)行輪詢的示意圖。select輪詢具有一個較弱的限制,那就是由于它采用一個1024長度的數(shù)組來存儲狀態(tài),也就是說它最多可以同時檢查1024個文件描述符。

poll

poll。poll比select有所改進(jìn),采用鏈表的方式避免數(shù)組長度的限制,其次它可以避免不必要的檢查。但是文件描述符較多的時候,它的性能是十分低下的。

epoll

該方案是Linux下效率最高的I/O事件通知機(jī)制,在進(jìn)入輪詢的時候如果沒有檢查到I/O事件,將會進(jìn)行休眠,直到事件發(fā)生將它喚醒。它是真實(shí)利用了事件通知,執(zhí)行回調(diào)的方式,而不是遍歷查詢,所以不會浪費(fèi)CPU,執(zhí)行效率較高。

除此之外, 另外的poll和select還具有以下的缺點(diǎn)(引用自 文章 ):

  • 每次調(diào)用select,都需要把fd集合從用戶態(tài)拷貝到內(nèi)核態(tài),這個開銷在fd很多時會很大
  • 同時每次調(diào)用select都需要在內(nèi)核遍歷傳遞進(jìn)來的所有fd,這個開銷在fd很多時也很大
  • select支持的文件描述符數(shù)量太小了,默認(rèn)是1024

epoll對于上述的改進(jìn)

epoll既然是對select和poll的改進(jìn),就應(yīng)該能避免上述的三個缺點(diǎn)。那epoll都是怎么解決的呢?在此之前,我們先看一下epoll和select和poll的調(diào)用接口上的不同,select和poll都只提供了一個函數(shù)——select或者poll函數(shù)。而epoll提供了三個函數(shù),epoll_create,epoll_ctl和epoll_wait,epoll_create是創(chuàng)建一個epoll句柄;epoll_ctl是注冊要監(jiān)聽的事件類型;epoll_wait則是等待事件的產(chǎn)生。

對于第一個缺點(diǎn),epoll的解決方案在epoll_ctl函數(shù)中。每次注冊新的事件到epoll句柄中時(在epoll_ctl中指定EPOLL_CTL_ADD),會把所有的fd拷貝進(jìn)內(nèi)核,而不是在epoll_wait的時候重復(fù)拷貝。epoll保證了每個fd在整個過程中只會拷貝一次。

對于第二個缺點(diǎn),epoll的解決方案不像select或poll一樣每次都把current輪流加入fd對應(yīng)的設(shè)備等待隊(duì)列中,而只在epoll_ctl時把current掛一遍(這一遍必不可少)并為每個fd指定一個回調(diào)函數(shù),當(dāng)設(shè)備就緒,喚醒等待隊(duì)列上的等待者時,就會調(diào)用這個回調(diào)函數(shù),而這個回調(diào)函數(shù)會把就緒的fd加入一個就緒鏈表)。epoll_wait的工作實(shí)際上就是在這個就緒鏈表中查看有沒有就緒的fd(利用schedule_timeout()實(shí)現(xiàn)睡一會,判斷一會的效果,和select實(shí)現(xiàn)中的第7步是類似的)。

對于第三個缺點(diǎn),epoll沒有這個限制,它所支持的FD上限是最大可以打開文件的數(shù)目,這個數(shù)字一般遠(yuǎn)大于2048,舉個例子,在1GB內(nèi)存的機(jī)器上大約是10萬左右,一般來說這個數(shù)目和系統(tǒng)內(nèi)存關(guān)系很大。

Node中的異步網(wǎng)絡(luò)Io就是利用了epoll來實(shí)現(xiàn), 簡單來說, 就是利用一個線程來管理眾多的IO請求, 通過事件機(jī)制實(shí)現(xiàn)消息通訊。

事件循環(huán)

理解了Node中磁盤IO和網(wǎng)絡(luò)IO的底層實(shí)現(xiàn)后, 基于上面的代碼, 可以看出Node是基于事件注冊的方式在完成Io后進(jìn)行一系列的處理, 其內(nèi)部是利用了事件循環(huán)的機(jī)制。

關(guān)于事件循環(huán), 是指JS在每次執(zhí)行完同步任務(wù)后會檢查執(zhí)行棧是否為空, 是的話就會去執(zhí)行注冊的事件列表, 不斷的循環(huán)該過程。Node中的事件循環(huán)有六個階段:

其中的每個階段都會處理相關(guān)的事件:

  • timers: 執(zhí)行setTimeout和setInterval中到期的callback。
  • pending callback: 執(zhí)行延遲到下一個循環(huán)迭代的 I/O 回調(diào)。
  • idle, prepare:僅系統(tǒng)內(nèi)部使用。
  • poll:檢索新的 I/O 事件;執(zhí)行與 I/O 相關(guān)的回調(diào)(幾乎所有情況下,除了關(guān)閉的回調(diào)函數(shù),它們由計(jì)時器和 setImmediate() 排定的之外),其余情況 node 將在此處阻塞。(即本文的內(nèi)容相關(guān)))
  • check: setImmediate() 回調(diào)函數(shù)在這里執(zhí)行。
  • close callbacks: 執(zhí)行close事件的callback,例如socket.on('close'[,fn])或者h(yuǎn)ttp.server.on('close, fn)。

ok, 這樣就解釋了Node是如何執(zhí)行我們注冊的事件, 那么還缺少一個環(huán)節(jié), Node又是怎么把事件和IO請求對應(yīng)起來呢? 這里涉及到了另外一種中間產(chǎn)物請求對象。

以打開一個文件為例子:

fs.open = function(path, flags, mode, callback){

//...

binding.open(pathModule._makeLong(path), stringToFlags(flags), mode, callback);

}

fs.open()的作用是根據(jù)指定路徑和參數(shù)去打開一個文件,從而得到一個文件描述符,這是后續(xù)所有I/O操作的初始操作。從前面的代碼中可以看到,JavaScript層面的代碼通過調(diào)用C++核心模塊進(jìn)行下層的操作。

從JavaScript調(diào)用Node的核心模塊,核心模塊調(diào)用C++內(nèi)建模塊,內(nèi)建模塊通過libuv進(jìn)行系統(tǒng)調(diào)用,這是Node里經(jīng)典的調(diào)用方式。這里libuv作為封裝層,有兩個平臺的實(shí)現(xiàn),實(shí)質(zhì)上是調(diào)用了uv_fs_open()方法。在uv_fs_open()的調(diào)用過程中,我們創(chuàng)建了一個FSReqWrap請求對象。從JavaScript層傳入的參數(shù)和當(dāng)前方法都被封裝在這個請求對象中,其中我們最為關(guān)注的回調(diào)函數(shù)則被設(shè)置在這個對象的oncomplete_sym屬性上:

req_wrap->object_->Set(oncomplete_sym, callback);

QueueUserWorkItem()方法接受3個參數(shù):第一個參數(shù)是將要執(zhí)行的方法的引用,這里引用的uv_fs_thread_proc;第二個參數(shù)是uv_fs_thread_proc方法運(yùn)行時所需要的參數(shù);第三個參數(shù)是執(zhí)行的標(biāo)志。當(dāng)線程池中有可用線程時,我們會調(diào)用uv_fs_thread_proc()方法。uv_fs_thread_proc()方法會根據(jù)傳入?yún)?shù)的類型調(diào)用相應(yīng)的底層函數(shù)。以uv_fs_open()為例,實(shí)際上調(diào)用fs_open()方法。

至此,JavaScript調(diào)用立即返回,由JavaScript層面發(fā)起的異步調(diào)用的第一階段就此結(jié)束。JavaScript線程可以繼續(xù)執(zhí)行當(dāng)前任務(wù)的后續(xù)操作。當(dāng)前的I/O操作在線程池中等待執(zhí)行,不管它是否阻塞I/O,都不會影響到JavaScript線程的后續(xù)執(zhí)行,如此就達(dá)到了異步的目的。

請求對象是異步I/O過程中的重要中間產(chǎn)物,所有的狀態(tài)都保存在這個對象中,包括送入線程池等待執(zhí)行以及I/O操作完畢后的回調(diào)處理。

關(guān)于這一塊其實(shí)個人認(rèn)為不用過于細(xì)究, 大致上知道有這么一個請求對象即可, 最后總結(jié)一下整個異步IO的流程:

圖引用自深入淺出NodeJs

至此, Node的整個異步Io流程都已經(jīng)清晰了, 它是依賴于IO線程池epoll、事件循環(huán)、請求對象共同構(gòu)成的一個管理機(jī)制。

Node為什么更適合IO密集

Node為人津津樂道的就是它更適合 IO密集型 的系統(tǒng), 并且具有 更好的性能 , 關(guān)于這一點(diǎn)其實(shí)與它的異步IO息息相關(guān)。

對于一個request而言, 如果我們依賴io的結(jié)果, 異步io和同步阻塞io(每線程/每請求)都是要等到io完成才能繼續(xù)執(zhí)行. 而同步阻塞io, 一旦阻塞就不會在獲得cpu時間片, 那么為什么異步的性能更好呢?

其根本原因在于同步阻塞Io需要為 每一個請求創(chuàng)建一個線程 , 在Io的時候, 線程被block, 雖然不消耗cpu, 但是其本身具有內(nèi)存開銷, 當(dāng)大并發(fā)的請求到來時, 內(nèi)存很快被用光, 導(dǎo)致服務(wù)器緩慢 , 在加上, 切換上下文代價也會消耗cpu資源 。而Node的異步Io是通過事件機(jī)制來處理的, 它不需要為每一個請求創(chuàng)建一個線程, 這就是為什么Node的性能更高。

特別是在Web這種IO密集型的情形下更具優(yōu)勢, 除開Node之外, 其實(shí)還有另外一種事件機(jī)制的服務(wù)器Ngnix, 如果明白了Node的機(jī)制對于Ngnix應(yīng)該會很容易理解, 有興趣的話推薦看這篇文章。

總結(jié)

在真正的學(xué)習(xí)Node異步IO之前, 經(jīng)??吹揭恍╆P(guān)于Node適不適合作為服務(wù)器端的開發(fā)語言的爭論, 當(dāng)然也有很多片面的說法。

其實(shí), 關(guān)于這個問題還是取決于你的業(yè)務(wù)場景。

假設(shè)你的業(yè)務(wù)是cpu密集型的, 那你采用Node來開發(fā), 肯定是不適合的。 為什么不適合? 因?yàn)镹ode是單線程, 你被阻塞在計(jì)算的時候, 其他的事件就做不了, 處理不了請求, 也處理不了回調(diào)。

那么在IO密集型中, Node就比Java好嗎? 其實(shí)也不一定, 還是要取決于你的業(yè)務(wù)。 如果你的業(yè)務(wù)是非常大的并發(fā), 但是你的服務(wù)器資源又有限, 就好比現(xiàn)在有個入口, Node可以一次進(jìn)10個人, 而Java依次排隊(duì)進(jìn)一個人, 如果是10個人同時進(jìn), 當(dāng)然是Node更具有優(yōu)勢, 但是假設(shè)有100個人(如1w個異步請求之類)的話, 那么Node就會因?yàn)樗漠惒綑C(jī)制導(dǎo)致應(yīng)用被掛起,內(nèi)存狂飆,IO堵塞,而且不可恢復(fù),這個時候你只能重啟了。而Java卻可以有序的處理, 雖然會慢一點(diǎn)。 而一臺服務(wù)器掛了造成的線上事故的損失更是不可衡量的。(當(dāng)然, 如果服務(wù)器資源足夠的話, Node也能處理)。

最后, 事實(shí)上Java也是具有異步IO的庫, 只是相對來說, Node的語法更自然更貼近, 也就更適合。

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • nodejs+express搭建多人聊天室步驟

    nodejs+express搭建多人聊天室步驟

    本篇文章給大家詳細(xì)講解了nodejs+express搭建一個簡易的多人聊天室的詳細(xì)步驟,有興趣的朋友學(xué)習(xí)下。
    2018-02-02
  • node實(shí)現(xiàn)爬蟲的幾種簡易方式

    node實(shí)現(xiàn)爬蟲的幾種簡易方式

    這篇文章主要給大家介紹了關(guān)于node實(shí)現(xiàn)爬蟲的幾種簡易方式,文中通過示例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用node具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-08-08
  • Node.js安裝詳細(xì)步驟教程(Windows版)詳解

    Node.js安裝詳細(xì)步驟教程(Windows版)詳解

    這篇文章主要介紹了Node.js安裝詳細(xì)步驟教程(Windows版),本文圖文并茂給大家介紹的非常詳細(xì),具有一定的參考借鑒價值,需要的朋友可以參考下
    2019-09-09
  • node中短信api實(shí)現(xiàn)驗(yàn)證碼登錄的示例代碼

    node中短信api實(shí)現(xiàn)驗(yàn)證碼登錄的示例代碼

    這篇文章主要介紹了node中短信api實(shí)現(xiàn)驗(yàn)證碼登錄的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-01-01
  • node.js中path路徑模塊的使用方法實(shí)例分析

    node.js中path路徑模塊的使用方法實(shí)例分析

    這篇文章主要介紹了node.js中path路徑模塊的使用方法,結(jié)合實(shí)例形式分析了node.js path路徑模塊的基本功能、原理、使用方法及操作注意事項(xiàng),需要的朋友可以參考下
    2020-02-02
  • NodeJS設(shè)計(jì)模式總結(jié)【單例模式,適配器模式,裝飾模式,觀察者模式】

    NodeJS設(shè)計(jì)模式總結(jié)【單例模式,適配器模式,裝飾模式,觀察者模式】

    這篇文章主要介紹了NodeJS設(shè)計(jì)模式,結(jié)合實(shí)例形式總結(jié)分析了nodejs單例模式,適配器模式,裝飾模式,觀察者模式的概念、原理與具體實(shí)現(xiàn)技巧,需要的朋友可以參考下
    2017-09-09
  • npm 更改默認(rèn)全局路徑以及國內(nèi)鏡像的方法

    npm 更改默認(rèn)全局路徑以及國內(nèi)鏡像的方法

    今天小編就為大家分享一篇npm 更改默認(rèn)全局路徑以及國內(nèi)鏡像的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-05-05
  • NodeJs 模仿SIP話機(jī)注冊的方法

    NodeJs 模仿SIP話機(jī)注冊的方法

    這篇文章主要介紹了NodeJs 模仿SIP話機(jī)注冊的方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-06-06
  • 刪除node_modules文件夾太慢的解決方案

    刪除node_modules文件夾太慢的解決方案

    這篇文章主要介紹了刪除node_modules文件夾太慢的解決方案,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-02-02
  • Node.js使用Express.Router的方法

    Node.js使用Express.Router的方法

    這篇文章主要為大家詳細(xì)介紹了Node.js使用Express.Router的方法 ,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-11-11

最新評論