摘要:使用了一個事件驅(qū)動非阻塞式的模型,使其輕量又高效。的包管理器,是全球最大的開源庫生態(tài)系統(tǒng)。按照這個定義,之前所述的阻塞,非阻塞,多路復(fù)用信號驅(qū)動都屬于同步。
系列文章
Nodejs高性能原理(上) --- 異步非阻塞事件驅(qū)動模型
Nodejs高性能原理(下) --- 事件循環(huán)詳解
終于開始我nodejs的博客生涯了,先從基本的原理講起.以前寫過一篇瀏覽器執(zhí)行機(jī)制的文章,和nodejs的相似之處還是挺多的,不熟悉可以去看看先.
Javascript執(zhí)行機(jī)制--單線程,同異步任務(wù),事件循環(huán)
寫下來之后可能還是有點懞,以后慢慢補充,也歡迎指正,特別是那篇翻譯文章后面已經(jīng)看不懂了.有人出手科普一下就好了.因為懶得動手做,整篇文章的圖片要么來源官網(wǎng),要么來源百度圖片.
補充: 當(dāng)前Nodejs版本10.3.0
PS:
2019/8/13 修改部分描述內(nèi)容
用官網(wǎng)的說法就是:
Node.js 是一個基于 Chrome V8 引擎的 JavaScript 運行環(huán)境。
Node.js 使用了一個事件驅(qū)動、非阻塞式 I/O 的模型,使其輕量又高效。
Node.js 的包管理器 npm,是全球最大的開源庫生態(tài)系統(tǒng)。
一三我就跳過不講了,那是外部條件因素,我們集中精力了解第二條.
摘抄自<<深入淺出nodejs>>
操作系統(tǒng)對計算機(jī)進(jìn)行了抽象,將所有輸入輸出設(shè)備抽象為文件.內(nèi)核在進(jìn)行文件I/O操作時,通過文件描述符進(jìn)行管理,而文件描述符類似于應(yīng)用程序與系統(tǒng)內(nèi)核之間的憑證.應(yīng)用程序如果需要進(jìn)行I/O調(diào)用,需要先打開文件描述符,然后再根據(jù)文件描述符去實現(xiàn)文件的數(shù)據(jù)讀寫.此處非阻塞I/O與阻塞I/O的區(qū)別在于阻塞I/O完成整個獲取數(shù)據(jù)的過程,而非阻塞I/O則不帶數(shù)據(jù)直接返回,要獲取數(shù)據(jù),還需要通過文件描述符再次讀取.
I/O是指磁盤文件系統(tǒng)或者數(shù)據(jù)庫的寫入和讀出,其中聽到一些名詞像異步,非阻塞,同步,阻塞之間好像是同一回事,實際效果而言又好像真的就是同一回事,但是從計算機(jī)內(nèi)核I/O來說真不是同一回事,為了更加全面講解這個點,我們可以把它們都列出來,分別是:
阻塞I/O(Blocking I/O)在發(fā)起I/O操作之后會一直阻塞著進(jìn)程不執(zhí)行其他操作,直到得到響應(yīng)或者超時為止;
例子: 調(diào)用一個進(jìn)行I/O操作的API請求時(如讀寫操作),一定要等待系統(tǒng)內(nèi)核層面完成所有操作如磁盤尋道,讀取數(shù)據(jù),復(fù)制數(shù)據(jù)到內(nèi)存等等;
優(yōu)點: 基本不占用 CPU 資源, 能保證操作結(jié)束或者數(shù)據(jù)返回;
缺點: 單進(jìn)程單請求,阻塞造成CPU無謂的等待沒法充分應(yīng)用;
發(fā)起I/O操作不等得到響應(yīng)或者超時就立即返回讓進(jìn)程繼續(xù)執(zhí)行其他操作;
例子: 調(diào)用一個進(jìn)行I/O操作的API請求時(如讀寫操作),不等待系統(tǒng)內(nèi)核層面完成所有操作如磁盤尋道,讀取數(shù)據(jù),復(fù)制數(shù)據(jù)到內(nèi)存等等就返回;
優(yōu)點: 提高性能減少等待時間;
缺點: 返回的僅僅是當(dāng)前調(diào)用狀態(tài),想要獲取完整數(shù)據(jù)需要重復(fù)去請求判斷操作是否完成造成CPU損耗,基本方法就是輪詢;
在并發(fā)量大的時候上面兩種肯定都不適用,于是應(yīng)運而生出多路復(fù)用,里面又分幾種模式
select將需要進(jìn)行I/O操作的socket添加到select中進(jìn)行監(jiān)聽,然后阻塞線程,等待操作完成或超時之后select系統(tǒng)被激活調(diào)用返回,線程再發(fā)起I/O操作
具體方式還是通過輪詢檢查所有的socket,因為單個進(jìn)程支持的最大文件描述符是1024,所以實際并發(fā)量低于這個數(shù)
優(yōu)點: 同個線程能執(zhí)行多個I/O,跨平臺支持
缺點: 原理上還是屬于阻塞,單個I/O的處理時間甚至高過阻塞I/O,需要輪詢并發(fā)量有限(1024);
同select機(jī)制類似,但是poll基于鏈表實現(xiàn),并發(fā)量沒有限制
優(yōu)點: 同個線程能執(zhí)行多個I/O,并發(fā)量沒有限制
缺點: 依然是遍歷鏈表檢查,效率低下;
針對前兩者的缺點進(jìn)行改進(jìn),通過callback回調(diào)通知機(jī)制.減少內(nèi)存開銷,不因并發(fā)量大而降低效率,linux下最高效率的I/O事件機(jī)制
優(yōu)點: 同個線程能執(zhí)行多個I/O,并發(fā)量遠(yuǎn)遠(yuǎn)超過1024且不影響性能
缺點: 并發(fā)量少的情況下效率可能不如前兩者;
應(yīng)用程序使用socket進(jìn)行信號驅(qū)動 I/O,并安裝一個信號處理函數(shù),進(jìn)程繼續(xù)運行并不阻塞。
當(dāng)數(shù)據(jù)準(zhǔn)備好時,進(jìn)程會收到一個 SIGIO 信號,可以在信號處理函數(shù)中調(diào)用 I/O 操作函數(shù)處理數(shù)據(jù)。
優(yōu)點: 執(zhí)行之后不需要阻塞進(jìn)程,當(dāng)收到信號再執(zhí)行操作提高資源利用
缺點: 并發(fā)量大的時候可能會因為信號隊列溢出導(dǎo)致沒法通知;
發(fā)起I/O操作之后會阻塞進(jìn)程直到得到響應(yīng)或者超時。按照這個定義,之前所述的阻塞I/O,非阻塞I/O,I/O多路復(fù)用, 信號驅(qū)動I/O都屬于同步I/O。
上面講的不管是等待完成所有操作還是通過輪詢等方式獲取操作結(jié)果,其實都是會阻塞著進(jìn)程,區(qū)別無非是中間等待時間怎么分配;
優(yōu)點: 編寫執(zhí)行順序一目了然;
缺點: 阻塞造成CPU無謂的等待或多余的查詢,沒法充分應(yīng)用;
直接返回繼續(xù)執(zhí)行下一條語句,當(dāng)I/O操作完成或數(shù)據(jù)返回時,以事件的形式通知執(zhí)行IO操作的進(jìn)程.
注意: 異步I/O跟信號驅(qū)動I/O除了同異步阻塞非阻塞的區(qū)別外,前者是通知進(jìn)程I/O操作什么時候完成,后者是通知進(jìn)程什么時候可以發(fā)起I/O操作;
優(yōu)點: 提高性能無需等待或查詢,會有通知信息;
缺點: 代碼閱讀和流程控制較為復(fù)雜;
(這里原本想直接過,但是相似性太高容易模糊就打算畫圖,因為太多又懶得話想去百度找張圖,然后找不齊,最終在一個文章找到一個更加清晰明了的示意圖,很無恥又不失禮貌的借用了)
流程圖來自于IO - 同步,異步,阻塞,非阻塞 (亡羊補牢篇)
阻塞I/O和非阻塞I/O區(qū)別在于:在I/O操作的完成或數(shù)據(jù)的返回前是等待還是返回!(可以理解成一直等還是分時間段等)
同步I/O和異步I/O區(qū)別在于 :在I/O操作的完成或數(shù)據(jù)的返回前會不會將進(jìn)程阻塞(或者說是主動查詢還是被動等待通知)!
用個生活化的例子就是等外賣吧
阻塞I/O: 白領(lǐng)A下完單就守著前臺服務(wù)員直到收到外賣才離開,后面其他人在排隊等他走開;
非阻塞I/O: 白領(lǐng)B下完單每隔一段時間就去詢問前臺服務(wù)員外賣好了沒,需要來回走多次并且也要排隊但是妨礙其他人的時間較少;
I/O多路復(fù)用: 白領(lǐng)A和B分別在兩個前臺服務(wù)員下單,廚房大叔先做好哪份外賣就交給對應(yīng)的服務(wù)員;
信號驅(qū)動I/O: 白領(lǐng)C想要下單,前臺服務(wù)員先問問廚房還有沒有材料,得到回復(fù)之后再幫白領(lǐng)C下單;
異步I/O: 白領(lǐng)E下完單拿了號就去干其他事,直到前臺服務(wù)員叫號告訴他外賣好了;
我們都知道Javascript在瀏覽器中是單線程執(zhí)行,JS引擎線程和GUI渲染線程是互斥的,如果你用同步方式加載資源的時候UI停止渲染,也不能進(jìn)行交互,你猜用戶會干嘛?
而使用異步加載的話就沒這問題了,這不僅僅是阻塞期間的體驗問題,還是加載時間的問題.
例如有兩段I/O代碼執(zhí)行分別需時a和b,一般:
同步執(zhí)行需時: a+b;
異步執(zhí)行需時: Math.max(a,b);
這就是為什么異步非阻塞I/O是nodejs的主要理念,因為I/O代價非常昂貴.
資源分配主流方法有兩種:
單線程串行依次執(zhí)行優(yōu)點: 編寫執(zhí)行順序一目了然;
缺點: 無法充分利用多核CPU;
優(yōu)點: 有效利用多核CPU;
缺點: 創(chuàng)建/切換線程開銷大,還有鎖,狀態(tài)同步等繁雜問題;
優(yōu)點: 免去鎖,狀態(tài)同步等繁雜問題,又能提高CPU利用率;
事件驅(qū)動事件是一種通過監(jiān)聽事件或狀態(tài)的變化而執(zhí)行回調(diào)函數(shù)的流程控制方法,一般步驟
確定響應(yīng)事件的元素;
為指定元素確定需要響應(yīng)的事件類型;
為指定元素的指定事件編寫相應(yīng)的事件處理程序;
將事件處理程序綁定到指定元素的指定事件;
我們就以每個入門必學(xué)的創(chuàng)建服務(wù)器為例子
http .createServer((req, res) => { let data = ""; req.on("data", chunk => (data += chunk)); req.on("end", () => { res.end(data); }); }) .listen(8080);
所謂的事件驅(qū)動就是nodejs里有個事件隊列,每個進(jìn)來的請求處理完就被關(guān)閉然后繼續(xù)服務(wù)下一個請求,當(dāng)這個請求完成會被推進(jìn)處理隊列,然后通過一種循環(huán)方式檢測隊列事件有沒變化,有就執(zhí)行相對應(yīng)的回調(diào)函數(shù),沒有就跳過到下一步,如此往復(fù).
(看看我在runoob看到的圖,一不小心又借用了.)
事件驅(qū)動非常高效可擴(kuò)展性非常強,因為一直接受請求而不等待任何讀寫操作,更加詳細(xì)內(nèi)容下面會講到.
這塊知識點是從<<深入淺出nodejs>>看到的.
四個共同構(gòu)成Node異步I/O模型的基本要素:事件循環(huán), 觀察者, 請求對象, 執(zhí)行回調(diào).
(因為涉及到底層語言和系統(tǒng)實現(xiàn)不同,我衹能根據(jù)內(nèi)容簡單說說過程,再多無能為力了)
進(jìn)程啟動之后node就會創(chuàng)建一個循環(huán),每執(zhí)行一次循環(huán)體的過程稱為Tick.每個Tick的過程就是看是否有事件待處理,有就取出事件及其相關(guān)回調(diào)執(zhí)行,然后再重復(fù)Tick,否則退出進(jìn)程.
(百度找到<<深入淺出nodejs>>書本里的示意圖)
Node.js 基本上所有的事件機(jī)制都是用設(shè)計模式中觀察者模式實現(xiàn),每個事件循環(huán)中有一個或多個的觀察者,通過詢問這些觀察者就能得知是否有事件需要進(jìn)行處理.
瀏覽器中的事件可能來源于界面的交互或者文件加載而產(chǎn)生,而Node主要來源于網(wǎng)絡(luò)請求,文件I/O等,這些產(chǎn)生的事件都有對應(yīng)的觀察者.
(window下基于IOCP創(chuàng)建,*nix基于多線程創(chuàng)建)
對于Node中異步I/O調(diào)用,從發(fā)起調(diào)用到內(nèi)核執(zhí)行完I/O操作的過渡過程中存在一種中間產(chǎn)物請求對象.
在Javascript層面代碼會調(diào)用C++核心模塊,核心模塊會調(diào)用內(nèi)建模塊通過libuv進(jìn)行系統(tǒng)調(diào)用.創(chuàng)建一個請求對象并將入?yún)⒑彤?dāng)前方法等所有狀態(tài)都封裝在請求對象,包括送入線程池等待執(zhí)行以及I/O操作完畢之后的回調(diào)處理.然后被推入線程池等待執(zhí)行,Javascript調(diào)用至此返回繼續(xù)執(zhí)行當(dāng)前任務(wù)的后續(xù)操作,第一階段完成.
(官方介紹: libuv is a multi-platform support library with a focus on asynchronous I/O. It was primarily developed for use by Node.js,相當(dāng)關(guān)鍵的東西)
(百度找到<<深入淺出nodejs>>書本里的示意圖)
線程池中的I/O操作調(diào)用完成之后會保存結(jié)果然后向IOCP(還記得上面說window下基于IOCP創(chuàng)建么)提交執(zhí)行狀態(tài)告知當(dāng)前對象操作完成并將線程歸還線程池.中間還動用到事件循環(huán)的觀察者,每次Tick都會調(diào)用IOCP相關(guān)的方法檢查線程池是否有執(zhí)行完的請求,有就將請求對象加入到I/O觀察者的隊列中當(dāng)作事件處理.至此整個異步I/O流程結(jié)束.
完整流程如下
(百度找到<<深入淺出nodejs>>書本里的示意圖)
<<深入淺出nodejs>>
runoob
IO - 同步,異步,阻塞,非阻塞 (亡羊補牢篇)
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/106644.html
摘要:異步和事件驅(qū)動注本文是對眾多博客的學(xué)習(xí)和總結(jié),可能存在理解錯誤。接觸有兩個月,對的兩大特性一直有點模糊,即異步和事件驅(qū)動。 nodejs 異步I/O和事件驅(qū)動 注:本文是對眾多博客的學(xué)習(xí)和總結(jié),可能存在理解錯誤。請帶著懷疑的眼光,同時如果有錯誤希望能指出。 接觸nodejs有兩個月,對nodejs的兩大特性一直有點模糊,即異步IO和事件驅(qū)動。通過對《深入淺出nodejs》和幾篇博客的閱...
摘要:兩個事件驅(qū)動模型服務(wù)器平均每秒處理的請求數(shù)為服務(wù)器的一倍,而內(nèi)存降低了一半。事件驅(qū)動模型的出現(xiàn),是為了解決傳統(tǒng)服務(wù)器與網(wǎng)絡(luò)工作負(fù)載的需求的不匹配,實現(xiàn)高度可伸縮服務(wù)器,并降低內(nèi)存開銷。 from http://oyanglul.us 本文基本上這為兩篇文章的翻譯和整合 -...
摘要:如果一個即時定時器是被一個正在執(zhí)行的回調(diào)排入隊列的,則該定時器直到下一次事件循環(huán)迭代才會被觸發(fā)。參數(shù)描述在事件循環(huán)的當(dāng)前回合結(jié)束時要調(diào)用的函數(shù)。事件輪詢隨后的調(diào)用,會在任何事件包括定時器之前運行。 系列文章 Nodejs高性能原理(上) --- 異步非阻塞事件驅(qū)動模型Nodejs高性能原理(下) --- 事件循環(huán)詳解 前言 終于開始我nodejs的博客生涯了,先從基本的原理講起.以前寫...
摘要:單線程使用單線程來運行,而不是向之類的其它服務(wù)器,每個請求將生產(chǎn)一個線程,這種方法避免了上下文切換和內(nèi)存中的大量執(zhí)行堆棧,這也是和其它服務(wù)器為解決上一個年,著名的并發(fā)連接問題而采用的方法。 showImg(https://segmentfault.com/img/remote/1460000019968794?w=1080&h=675);當(dāng)我們學(xué)習(xí)一項新的事物的時候,我們首先要知道它來...
摘要:簡介項目命名為就是一個服務(wù)器單純開發(fā)一個服務(wù)器的想法,變成構(gòu)建網(wǎng)絡(luò)應(yīng)用的一個基本框架發(fā)展為一個強制不共享任何資源的單線程,單進(jìn)程系統(tǒng)。單線程弱點無法利用多核錯誤會引起整個應(yīng)用退出,應(yīng)用的健壯性大量計算占用導(dǎo)致無法繼續(xù)調(diào)用異步。 NodeJs簡介 Ryan Dahl項目命名為:web.js 就是一個Web服務(wù)器.單純開發(fā)一個Web服務(wù)器的想法,變成構(gòu)建網(wǎng)絡(luò)應(yīng)用的一個基本框架.Node發(fā)展...
閱讀 2293·2021-11-24 09:39
閱讀 2535·2021-11-22 15:24
閱讀 2976·2021-09-02 09:48
閱讀 3010·2021-07-26 22:01
閱讀 1433·2019-08-30 11:09
閱讀 1673·2019-08-29 18:47
閱讀 601·2019-08-29 15:40
閱讀 2132·2019-08-29 15:22