node.js 的第一個基本觀點是,I/O 操做是昂貴的:javascript
目前的編程技術最大的浪費來自等待 I/O 操做的完成。有幾種方法能夠解決這些對性能的影響(來自 Sam Rushing):
同步:依次處理單個請求。
優勢:簡單。
缺點:任何一個請求都會阻塞其他請求。
建立新進程:爲每一個請求建立一個進程處理
優勢:容易。
缺點:擴展性很差,數百個鏈接意味着數百個進程。fork()是 Unix 程序員的錘子。由於它頗有用,全部的問題都像是釘子。但這一般是多餘的。
線程:爲每一個請求建立一個線程處理。
優勢:容易;因爲線程的開銷一般都很小,相比於使用 fork 對內核更友好。
缺點:你的機器可能沒有線程,而且線程編程很容易變得複雜,也存在如何訪問共享資源的問題。html
第二個基本觀點是,單線程鏈接很是消耗內存。java
Apach 是多線程的:爲每個請求建立一個線程(或者進程,這取決於配置)。你能夠看到增長當前鏈接數是如何消耗內存的,多個線程須要同時服務多個客戶。Nginx 和 Node.js 不是多線程的,由於多線程和多進程會帶來沉重的內存開銷。它們是單線程的,可是基於事件的。經過單線程處理多個鏈接,解決數千個線程/進程的開銷問題。node
Node.js 爲代碼保持着單線程的運行環境git
Node.js 確實是單線程運行的:你不能執行任何併發代碼;例如「sleep」,這會使服務器中止。程序員
當代碼運行時,node.js不會響應客戶端的其餘請求,由於它只有一個線程在執行代碼。或者你可使用一些 CPU-密集型代碼,例如,調整圖片尺寸,這仍然會阻塞其餘請求。github
然而,一切代碼都能並行執行web
並無辦法讓代碼在單線程中並行運行。除了全部的 I/O 操做和異步事件,如下代碼並不會阻塞服務器:[codesyntax lang="javascript"]
數據庫
在一個請求中執行以上代碼,數據庫在休眠時其餘請求也能被很好的處理。apache
這樣有什麼好處?咱們何時應該將同步改成異步/並行執行?
同步執行是好方法,由於這使代碼編寫變得簡單(與多線程相比,併發問題致使了 WTFs)。
在 node.js 中,你不須要擔憂後臺會發生什麼:只須要使用回調執行 I/O 操做;這保證了你的代碼不會被中斷,同時 I/O 操做不會阻塞其餘請求,每一個請求也不會增長線程/進程的開銷(例如,Apache 中的內存開銷)。
異步 I/O 操做也是好方法,由於 I/O 操做相較於大多數代碼的執行更昂貴,咱們應該作其餘的事情,而不是等待 I/O 操做
時間循環是「一個可以加工和處理外部事件並將它們轉換爲回調調用的實體」。所以 I/O 調用的關鍵在於 Node.js 可以從一個請求切換到另外一個請求。在一個 I/O 調用中,代碼會保存回調函數,並將控制權返回給 node.js 的運行時環境。當數據可用時回調函數將被調用。
固然,在後臺中,有用於數據庫訪問和執行進程的線程和進程。然而,這並無使代碼暴露,所以你不須要爲 I/O 操做擔憂,例如,數據庫或者其餘進程對於每個請求都是異步的,這些線程的執行結果會經過事件循環返回給代碼。與 Apache 模式相比,不須要爲每一個鏈接提供單個線程,所以須要更少的線程和線程開銷;只有當真的須要並行運行時,即便管理權在 Node.js 也可以運行。
除了 I/O 操做的調用,Node.js 但願其餘的全部請求都能迅速響應;例如:CPU-密集型工做應該被拆分到交互事件的進程中,或者像 WebWorkers 那樣抽象的使用。(顯然地)這意味着在後臺沒有其餘的線程併發運行交互事件。基本上,全部的監聽事件對象(都是 EventEmitter 的實例)都支持異步交互事件,你可以以這種方式與阻塞代碼交互,例如使用 files,sockets 或者子進程,這些在 Node.js 中都是 EventEmitters。[多核][8]也可使用這種方法,請參見:node-http-proxy
內部實現
在內部,node.js 依賴於 libev 實現事件循環,以 libeio 爲輔助,使用混合線程實現異步 I/O 操做。要想學習更多,就須要查看 libev 的文檔。
如何在 Node.js 中使用異步?
Tim Caswell 在他出色的演講中描述了這種模式:
First-class 函數。例如,咱們將函數做爲參數傳遞,在須要的時候執行他們。
Function 形式。也被稱做匿名函數或者閉包函數,當 I/0 操做完成後執行。