亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 開發 > JS > 正文

淺談Node 異步IO和事件循環

2024-05-06 16:50:31
字體:
來源:轉載
供稿:網友

前言

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

一些概念

 同步異步 & 阻塞非阻塞

查閱資料的時候, 發現很多人都對 異步和非阻塞 的概念有點混淆, 其實兩者是完全不同的, 同步異步指的是 行為即兩者之間的關系 , 而阻塞非阻塞指的是 狀態即某一方 。

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

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

同步異步

如果是同步的話, 那么應該是client發起請求后, 一直等到serve處理請求完成后才返回繼續執行后續的邏輯, 這樣 client和serve之間就保持了同步的狀態 。

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

阻塞非阻塞

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

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

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

總結

之所以經常會混亂是因為沒有說清楚討論的是哪一部分(下面會提到), 所以 同步異步討論的對象是雙方, 而阻塞非阻塞討論的對象是自身 。

IO和CPU

Io和Cpu是可以同時進行工作的 。

IO:

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

cpu

解釋計算機指令以及處理計算機軟件中的數據。

Node中的異步IO模型

IO分為 磁盤IO和網絡IO , 其具有兩個步驟

  1. 等待數據準備 (Waiting for the data to be ready)
  2. 將數據從內核拷貝到進程中 (Copying the data from the kernel to the process)

Node中的磁盤Io

以下的討論基于*nix系統。

理想的異步Io應該像上面討論的一樣, 如圖:

Node,異步IO,事件循環

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

Node,異步IO,事件循環

而在window下, 則是利用 IOCP 接口來完成, IOCP從用戶的角度來說確實是完美的異步調用方式, 而實際也是利用內核中的線程池, 其與nix系統的不同在于后者的線程池是用戶層提供的線程池。

Node中的網絡Io

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

阻塞 I/O(blocking IO)

Node,異步IO,事件循環

所以,blocking IO的特點就是在IO執行的兩個階段都被block了。

非阻塞 I/O(nonblocking IO)

Node,異步IO,事件循環

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

I/O 多路復用( IO multiplexing)

Node,異步IO,事件循環

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

異步 I/O(asynchronous IO)

Node,異步IO,事件循環

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

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

read

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

Node,異步IO,事件循環

select

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

Node,異步IO,事件循環

poll

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

Node,異步IO,事件循環

epoll

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

Node,異步IO,事件循環

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

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

epoll對于上述的改進

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

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

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

對于第三個缺點,epoll沒有這個限制,它所支持的FD上限是最大可以打開文件的數目,這個數字一般遠大于2048,舉個例子,在1GB內存的機器上大約是10萬左右,一般來說這個數目和系統內存關系很大。

Node中的異步網絡Io就是利用了epoll來實現, 簡單來說, 就是利用一個線程來管理眾多的IO請求, 通過事件機制實現消息通訊。

事件循環

理解了Node中磁盤IO和網絡IO的底層實現后, 基于上面的代碼, 可以看出Node是基于事件注冊的方式在完成Io后進行一系列的處理, 其內部是利用了事件循環的機制。

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

Node,異步IO,事件循環

其中的每個階段都會處理相關的事件:

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

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

以打開一個文件為例子:

fs.open = function(path, flags, mode, callback){//...binding.open(pathModule._makeLong(path), stringToFlags(flags), mode, callback);}

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

Node,異步IO,事件循環

從JavaScript調用Node的核心模塊,核心模塊調用C++內建模塊,內建模塊通過libuv進行系統調用,這是Node里經典的調用方式。這里libuv作為封裝層,有兩個平臺的實現,實質上是調用了uv_fs_open()方法。在uv_fs_open()的調用過程中,我們創建了一個FSReqWrap請求對象。從JavaScript層傳入的參數和當前方法都被封裝在這個請求對象中,其中我們最為關注的回調函數則被設置在這個對象的oncomplete_sym屬性上:

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

QueueUserWorkItem()方法接受3個參數:第一個參數是將要執行的方法的引用,這里引用的uv_fs_thread_proc;第二個參數是uv_fs_thread_proc方法運行時所需要的參數;第三個參數是執行的標志。當線程池中有可用線程時,我們會調用uv_fs_thread_proc()方法。uv_fs_thread_proc()方法會根據傳入參數的類型調用相應的底層函數。以uv_fs_open()為例,實際上調用fs_open()方法。

至此,JavaScript調用立即返回,由JavaScript層面發起的異步調用的第一階段就此結束。JavaScript線程可以繼續執行當前任務的后續操作。當前的I/O操作在線程池中等待執行,不管它是否阻塞I/O,都不會影響到JavaScript線程的后續執行,如此就達到了異步的目的。

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

關于這一塊其實個人認為不用過于細究, 大致上知道有這么一個請求對象即可, 最后總結一下整個異步IO的流程:

Node,異步IO,事件循環

圖引用自深入淺出NodeJs

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

Node為什么更適合IO密集

Node為人津津樂道的就是它更適合 IO密集型 的系統, 并且具有 更好的性能 , 關于這一點其實與它的異步IO息息相關。

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

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

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

總結

在真正的學習Node異步IO之前, 經常看到一些關于Node適不適合作為服務器端的開發語言的爭論, 當然也有很多片面的說法。

其實, 關于這個問題還是取決于你的業務場景。

假設你的業務是cpu密集型的, 那你采用Node來開發, 肯定是不適合的。 為什么不適合? 因為Node是單線程, 你被阻塞在計算的時候, 其他的事件就做不了, 處理不了請求, 也處理不了回調。

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

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

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持VeVb武林網。


注:相關教程知識閱讀請移步到JavaScript/Ajax教程頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
精品国产自在精品国产浪潮| 在线播放国产一区二区三区| 国产在线观看精品一区二区三区| 国产精品入口免费视| 97在线视频一区| 日韩av123| 久国内精品在线| 日本最新高清不卡中文字幕| 亚州国产精品久久久| 亚洲国产成人在线视频| 91久久久精品| 亚洲国产婷婷香蕉久久久久久| 欧美小视频在线| 国产亚洲在线播放| 国产一区二区三区在线| 国产精品一区二区在线| 国产视频丨精品|在线观看| 91丨九色丨国产在线| 久久亚洲精品毛片| 亚洲wwwav| 国产裸体写真av一区二区| 亚洲最大激情中文字幕| 日韩理论片久久| 亚洲欧美日韩视频一区| 亚洲成人免费在线视频| 精品视频9999| 国产成人精品一区二区三区| 日韩av观看网址| 精品露脸国产偷人在视频| 日韩一区二区三区国产| 日韩精品中文字幕在线| 亚洲福利在线观看| 一本色道久久综合狠狠躁篇怎么玩| 久久久久99精品久久久久| 一本色道久久88综合日韩精品| 国产亚洲精品一区二555| 日韩经典第一页| 欧美激情在线狂野欧美精品| 国产欧美在线看| 97国产一区二区精品久久呦| 国产91亚洲精品| 成人欧美一区二区三区在线湿哒哒| 91中文字幕在线观看| 中文日韩电影网站| 宅男66日本亚洲欧美视频| 激情久久av一区av二区av三区| 91黑丝高跟在线| 国产精品露脸自拍| 日韩av成人在线观看| 欧美日韩国产中文精品字幕自在自线| 欧美日韩亚洲国产一区| 国产做受69高潮| 亚洲第一级黄色片| 97欧美精品一区二区三区| 日本久久久久久久久久久| 久久久成人精品| 欧美日韩国产精品一区二区不卡中文| 久久久av免费| 色偷偷av亚洲男人的天堂| 91av成人在线| 九九久久久久99精品| 97超级碰在线看视频免费在线看| 亚洲国产精品久久久久秋霞蜜臀| 丰满岳妇乱一区二区三区| 91系列在线播放| 成人黄色大片在线免费观看| 国产精品主播视频| 2019亚洲男人天堂| 欧美三级免费观看| 亚洲美女在线看| 九九久久精品一区| 亚洲自拍小视频免费观看| 亚洲性线免费观看视频成熟| 欧美一级视频在线观看| 国内免费久久久久久久久久久| 日韩久久免费视频| 亚洲美女又黄又爽在线观看| 亚洲欧美另类国产| 成人免费xxxxx在线观看| 成人www视频在线观看| 北条麻妃一区二区三区中文字幕| 亚洲精品免费在线视频| 日韩免费在线观看视频| 国产在线播放91| 久久久人成影片一区二区三区观看| 国产日本欧美视频| 8090成年在线看片午夜| 成人亚洲激情网| 国产成人极品视频| 亚洲国产日韩欧美在线99| 黑人精品xxx一区一二区| 在线观看欧美视频| 国产日本欧美一区二区三区| 亚洲国内高清视频| 国产在线视频2019最新视频| 国产精品国产三级国产aⅴ浪潮| 黄网站色欧美视频| 精品国产欧美成人夜夜嗨| 92国产精品视频| 亚洲丁香婷深爱综合| 欧美亚洲视频在线看网址| 国产一级揄自揄精品视频| 国产成人精品视频在线| 色琪琪综合男人的天堂aⅴ视频| 国产精品免费一区豆花| 国产精品一区二区三区在线播放| 日韩免费观看av| 欧美成人免费在线视频| 性欧美xxxx视频在线观看| 欧美猛交免费看| 日本久久精品视频| 欧美理论片在线观看| 青青久久av北条麻妃黑人| 亲爱的老师9免费观看全集电视剧| 韩国视频理论视频久久| 亚洲色图欧美制服丝袜另类第一页| 国产一区欧美二区三区| 色噜噜亚洲精品中文字幕| 亚洲欧美综合另类中字| 日本成人激情视频| 精品久久久久久亚洲精品| 91欧美视频网站| 日本一区二区不卡| 日韩黄色在线免费观看| 欧美精品免费播放| 亚洲bt欧美bt日本bt| 中文字幕在线看视频国产欧美在线看完整| 亚洲欧美在线免费观看| 亚洲午夜精品视频| 日韩的一区二区| 91精品久久久久久久久久| 成人有码视频在线播放| 97成人在线视频| 欧美高跟鞋交xxxxhd| 狠狠久久亚洲欧美专区| 欧美激情极品视频| 久久久久久久国产精品视频| 亚洲影院污污.| 欧美性猛交丰臀xxxxx网站| 555www成人网| 亚洲第一av在线| 国产69精品久久久久99| 久久久999精品免费| 国产欧美va欧美va香蕉在线| 国产精品福利无圣光在线一区| 国产在线a不卡| 久热精品视频在线观看一区| 国产精品一区av| 91精品国产综合久久香蕉的用户体验| 精品久久久久人成| 国产成人精品网站| 欧美极品少妇xxxxx| 日韩欧美999| 国产精品视频一区二区三区四| 国产欧美在线播放| 亚洲精品动漫久久久久| 91精品综合视频| 亚洲毛茸茸少妇高潮呻吟| 狠狠久久五月精品中文字幕| 97视频在线免费观看| 国产亚洲精品久久久优势| 精品在线欧美视频| 国产精品夜间视频香蕉| 欧美成人精品不卡视频在线观看|