Node.js之異步編程


文章原創於公眾號:程序猿周先森。本平台不定時更新,喜歡我的文章,歡迎關注我的微信公眾號。
file

其實對大部分的開發者來說,異步編程與一般自然語言的線性思維會有所沖突。所以大部分開發者不能適應直接面向事件驅動進行編程,Node.js是首個將異步編程帶到應用層面的平台,Node.js無時無刻不透露出異步的信息。在接觸Node的過程中,很多人只是很粗略的接觸了幾個回調函數之后就放棄了,確實Node使用異步編程,容易陷入回調地獄,但是Node異步編程的難題其實已經基本解決,可以通過事件發布/訂閱模式,或者通過Promise/Defferred模式,其實都可以完美的去解決回調陷阱的問題。

其實大部分開發人員都習慣線性思維去思考問題,所以同步編程一直很流行。但是單線程同步模型中,CPU與I/O操作無法重疊進行,所以性能問題也就擺在了開發人員的面前。在大多數語言中,提高性能的方式一般使用多線程的方式解決,但是多線程中線程切換耗費的開銷,以及鎖以及線程同步等問題,所以多線程會給開發人員業務邏輯帶來麻煩。而Node直接采用異步編程,可以使CPU與I/O操作並行不用相互等待,可以讓資源等到更好的利用。

異步IO與非阻塞IO的區別

非阻塞IO是由於完整的I/O沒有完成,立即返回的並不是我們執行的最終數據,而僅僅是返回當前的調用狀態,為了獲得完整數據需要進行輪詢重復調用I/O操作確認是否完成。異步I/O可實現不等待數據讀取完成,執行I/O操作后后立刻返回,數據寫入緩存,由底層完成監聽操作,並返回成功或失敗的信息給應用。

異步編程優點

Node.js最大的優點莫過於基於事件驅動的非阻塞I/O模型,非阻塞I/O可以使CPU與I/O操作不用相互等待,可以讓資源可以得到更好的利用。Node.js為了解決編程模型中阻塞I/O的性能問題,采用單線程異步模型,所以Node.js更適合I/O密集型問題,因為Node.js面向驅動進行編程,所以需要面對海量請求,當海量請求同時作用在單線程上時,就需要防止任何一個會過度消耗時間片的請求。所以只要合理利用Node.js的異步模型,加上V8引擎的高性能,就可以充分發揮CPU與I/O並行的優勢。

異步編程的難點

Node.js借助異步I/O模型以及V8引擎,突破了單線程的性能瓶頸,讓JavaScript在后端體現了實用價值。但是也由於異步編程會給開發者帶來一些難點。

(1)函數嵌套過深

在前端JavScript中,DOM事件綁定一般較少存在多重事件綁定的情況。一般為不同的DOM元素綁定不同的事件。
file

但是對於Node.js而言,多個異步調用的場景比比皆是。
file

其實對於最后的結果來說這樣的函數嵌套結構是沒有任何問題的,但是這樣並沒有利用好Node.js異步I/O帶來的並行優勢。而且函數嵌套過深,對於開發人員的后期維護也會造成困難。

(2)阻塞代碼

在JavaScript中,並沒有類似Java的sleep()這樣的線程沉睡功能,可以進行延時操作的只有setInterval()和setTimeout()這兩個函數。那么如果我們需要在JavaScript中實現延遲1s要怎么做呢?其實大部分開發者可能會這么去進行實現:
file

但是請記住Node.js是單線程模型,所以在執行的時候CPU資源會全部為了這段代碼進行服務。從而導致其他請求全部被視而不見。所以我們可以采用setTimeout改寫一下代碼效果會更好:
file

但是這里有一個問題,如果我把后面的時間設成0,是不是意味着馬上執行代碼呢?這個問題大家可以思考一下,對答案感興趣的可以直接在公眾號發消息,我會及時回復。

(3)多線程編程

因為Node.js是單線程模型,對於多核CPU服務端而言,其實Node.js單進程是沒有充分利用好多核CPU的,所以瀏覽器可以將JavaScript與UI渲染分離,就可以更好的去利用多核CPU為大量計算做服務。但是這種開發模式開發者要面臨跨線程的編程,對於JavaScript一直走的單線程編程路線來說會增加一定的難度。

(4)異常處理

我們在使用Java進行異常處理其實是非常方便的,可以直接通過try/catch/finally語句進行異常捕獲以及異常處理。
file

但是在異步編程中這種常用的異常處理並不一定適用,因為前面有講過異步編程時異步I/O提交請求后馬上返回,因為異常一般不會發生在此階段,這時候你對這段代碼執行try/catch操作進行異常捕獲其實不會發揮作用,因為try/catch只能捕獲當次事件內發生的異常,對事件執行結束返回的回調函數callback中拋出的異常其實是無能為力的,所以在Node.js中,將異常作為回調函數callback的第一個參數傳回,如果為空時,則表示回調函數沒有拋出任何異常。
file

上述代碼中,執行checkLogin如果出現異常,則回調函數的第一個參數err則不為空,我們就可以根據這個err參數對異常進行處理。

異步編程解決方案

  1. 事件發布/訂閱模式

  2. Promise/Deferred模式

  3. 流程控制庫

由於這三種方案涉及知識點較雜,這篇文章暫時不對這三種方案作具體介紹,下一篇文章會對這三種方案作具體介紹。

異步並發控制

在Node.js中,我們可以很輕易的利用異步發起並行調用,但是如果並發量過大,我們的服務器會承受不住,比如如果是對文件系統進行大量並發調用,操作系統的文件描述符數量會在瞬間被用光。所以對於異步編程來說並發很容易實現,但是也要有一定的過載保護。這里主要講一種過載解決方案:async.

async提供了一個方法parallelLimit()用於處理異步調用的限制。
file

parallelLimit()方法有一個用於限制並發數量的參數,使得任務只能同時並發一定數量,而不能無限量同時並發。上面的代碼,我們並發數設置為1,所以只能同時並發一個任務。

但是parallelLimit()有一個缺點:無法動態的添加並行任務。但是async提供了queue()方法可以動態添加並行任務,這對於遍歷文件目錄等操作是非常高效的。但是queue()接收的參數是固定的,丟失了parallelLimit()的多樣性。
file

本篇到這里內容就結束了,本篇主要還是偏概念,可能得對Node.js有一定了解才更適合,所以這里推薦csdn一篇比較基礎的關於異步編程的文章:https://blog.csdn.net/O4dC8OjO7ZL6/article/details/79987819

歡迎關注我個人公眾號:程序猿周先森
file


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM