關於node.js的誤會


昨天寫了篇博客,介紹了一下我對node.js的第一次親密接觸后的感受,以為node.js很小眾,出乎我意料很多人感興趣,並且對博客中的細節問題做了評論,最多的是圍繞node.js的異步與單線程展開的,當然還有很多關於node.js究竟是不是語言?不是的話又是什么。。。之類的問題,其實剛接觸node.js,了解的並不是很深入,越是回復大家問題,心里越是沒底,決定認真研究一下,經人指點看了一下《Node.js開發指南》發現大部分問題都有了答案,權當一個讀書筆記把問題答案分享出來,希望可以幫到一些和我一樣才接觸node.js的小菜

 關於單線程一個由來已久的誤會

在上篇博客中提到我們使用node.js寫的JavaScript代碼是單線程運行的,讓很多同學很疑惑,單線程怎么實現異步操作,單線程誰去響應事件。。。在html5 Web Workers中我也有提到過客戶端的JavaScript也是單線程運行的,大家明顯沒有這么大反應,還是普遍能接受的。可單線程的客戶端JavaScript也能響應DOM事件,還有大家都很熟悉的ajax操作,回調函數也是異步的,既然客戶端JavaScript是單線程執行的,回調函數是誰調用的呢?答案很簡單,JavaScript的宿主環境——瀏覽器,也就是說雖然JavaScript是單線程執行的,但瀏覽器是多線程的,負責調度管理JavaScript代碼,讓它們在恰當的時機執行。

所以我們所說的node.js單線程,是指node.js並沒有給我們創建一個線程的能力,所有我們自己寫的代碼都是單線程執行的,在同一時間內,只能執行我們寫的一句代碼。但宿主環境node.js並不是單線程的,它會維護一個執行隊列,循環檢測,調度JavaScript線程來執行。因此單線程執行和並發操作並不沖突。

阻塞與線程

什么叫阻塞(block)?線程在執行中如果遇到I/O操作(磁盤讀寫、網絡通信等)通常需要耗費較長的時間,這時候操作系統會剝奪線程對CPU的控制權,使其暫停,並把資源讓給其它的工作線程,這種線程調度方式成為阻塞。當I/O操作完畢的時候操作系統將這個線程的阻塞狀態解除,恢復其對CPU的控制權,令其繼續執行,這種I/O模式就是同步I/O或成為阻塞I/O。

響應的異步I/O或非阻塞I/O則針對所有的I/O操作采取不阻塞的策略,當線程遇到I/O操作時不會以阻塞的方式等待I/O操作結束,而只是將I/O請求發送給操作系統,繼續執行后續語句。當操作系統完成I/O操作時以事件的形式通知執行I/O操作的線程,線程會在特定時間處理這個事件。為了處理異步I/O必須有事件循環,不斷檢查有沒有未處理的事件,依次予以處理。

在阻塞模式下,一個線程只能處理一個任務,要想提高吞吐量必須通過多線程。而在非阻塞模式下一個線程永遠在執行計算操作,這個線程所使用的CPU核心利用率永遠是100%,I/O以事件的方式通知。在阻塞模式下多線程往往能夠提高系統吞吐量,因為一個線程阻塞時還有其他線程在工作,多線程何以讓CPU資源不被阻塞的線程浪費。而在非阻塞模式下,線程不會被I/O阻塞,永遠在利用CPU。異步I/O減少了多線程中創建線程、分配內存、列入調度、切換線程、內存換頁、CPU緩存等方面的開銷。

事件循環機制

上面提到了幾次事件循環機制,那么這個聽起來貌似很高端的東東究竟是什么呢?所謂事件循環是指node.js會把所有的異步操作使用事件機制解決,有個線程在不斷地循環檢測事件隊列。node.js中所有的邏輯都是事件的回調函數,所以node.js始終在事件循環中,程序入口就是事件循環第一個事件的回調函數。事件的回調函數中可能會發出I/O請求或直接發射( emit)事件,執行完畢后返回事件循環。事件循環會檢查事件隊列中有沒有未處理的事件,直到程序結束。node.js的事件循環對開發者不可見,由libev庫實現,libev不斷檢查是否有活動的、可供檢測的事件監聽器,直到檢查不到時才退出事件循環,程序結束。

node.js是什么?和JavaScript有什么關系?

 關於node.js究竟是什么,大家的問題在於

  1. node.js是不是一門語言?
  2. node.js是不是一個JavaScript庫函數?
  3. node.js是不是一個JavaScript框架?

 很遺憾,這三個問題的答案都是NO,看看官方對自己的描述

Node.js is a platform built on Chrome's JavaScript runtime for easily building fast, scalable network applications. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient, perfect for data-intensive real-time applications that run across distributed devices.

官方很明確地說node.js是一個platform,也就是一個做xxx的平台。node.js是一個可以在服務器端運行JavaScript的平台,其實這句話華也不准確,按照《JavaScript權威指南》《JavaScript高級程序設計》等書中的定義,JavaScript是由兩部分組成

  1. core JavaScript
  2. client JavaScript(DOM、BOM)

而只有core JavaScript可以在node.js上運行,所以node.js借用了JavaScript的語法,但並不能用來處理瀏覽器對象(BOM)及文檔對象(DOM),所以node.js並不是設計為在服務器端運行解析html文檔的(當然有module可以做此事),所以“在服務器端運行的JavaScript”在一定程度上誤導了初學者。

同時node.js並不僅僅運行core JavaScript,node.js中還有文件系統、模塊包、操作系統API、網絡通信、二進制類型處理等core JavaScript不具備的功能。

node.js是在執行JavaScript語句嗎

從字面意思上是的,因為我們在使用node.js開發的時候寫的確實是JavaScript語句,而且node.js利用Google的V8 Javascript 引擎來解析JavaScript語句,但系統真正調用執行的代碼是用C++寫的,我們做的只是用JavaScript語句來調用這些底層API,所以不用擔心其執行效率過低問題

異步I/O與事件驅動

 毫不誇張的說node.js最大的特定就是采用異步I/O和事件驅動架構,對於高並發解決方案傳統架構師多線程模型,為每個業務邏輯童工一個線程,通過系統線程切換來來彌補同步I/O調用的時間開銷。node.js使用的是單線程模型,對所有I/O都采用異步式的請求方式,避免頻繁的上下文切換,在node.js執行的時候維護着一個事件隊列,程序在執行時進入事件循環等待下一個事件到來,每個異步I/O請求完成后都會被推送到事件隊列中的等待執行。

對於一個簡單的數據庫訪問操作,傳統方式是這樣實現的

 res = db.query('SELECT * from some_table');
 res.output();

 

代碼執行到第一行的時候線程會阻塞,等待query返回結果,然后繼續處理。由於數據庫查詢、磁盤讀寫、網絡通信等原因阻塞時間會非常大(相對於CPU始終頻率)。對於高並發的訪問,一方面線程長期阻塞等待,另一方面為了應付新情求而不斷添加新線程,會浪費大量系統資源,同時線程的增加也會也會占用大量的CPU時間來處理內存上下文切換。看看node.js怎么處理

db.query('SELECT * from some_table', function(res) { 
res.output(); });

 

在代碼中熟悉Javascript的同學一眼就可以看明白query的第二個參數是一個回調函數,進程執行到db.query的時候不會等待結果返回,而是直接繼續執行下面的語句,直到進入事件循環。當數據庫執行結果返回的時候會將事件發送到事件隊列,等到線程進入事件循環后才會調用之前的回調函數。

node.js的異步機制是基於事件的,所有的I/O、網絡通信、數據庫查詢都以非阻塞的方式執行,返回結果由事件循環來處理。node.js在同一時刻只會處理一個事件,完成后立即進入事件循環檢查后面事件。這樣CPU和內存在同一時間集中處理一件事,同時盡量讓耗時的I/O等操作並行執行。

node.js架構

node.js用異步式I/O和事件驅動代替多線程提升性能,除了使用高效的V8作為JavaScript引擎外還使用了高效的libev和libeio庫支持事件驅動和異步I/O。

node.js作者在libeio和libev的基礎上抽象出了libuv層,對於POSIX(Portable Operating System Interface 是一套操作系統API規范,遵循的有Unix、Linux、Mac OS X等)操作系統libuv通過封裝libev和libio來利用epoll或kqueue。而在Windows下libuv使用了IOCP(Input/Output Completion Port,輸入輸出完􏰛端)機制 在不同平台下實現高性能。

了解了這個就不要再以為node.js是利用JavaScript來操作系統了

為什么要使用node.js

其實這個問題可以歸結為node.js有什么特長,除了語法上讓熟悉JavaScript的人很舒服。相對於Javascript為客戶端而生,node.js為網絡而生,一切都以http為主,其內建HTTP服務器支持,使用node.js可以輕易地搭建一個網站和服務器組合,而不用想使用PHP還需要額外的Apache服務器,通過特有模塊或CGI調用才能將PHP腳本結果返回給用戶。

node.js還可以部署到非網絡應用環境下,因為其可以調用C/C++代碼,充分利用現有函數庫,在性能上有很大優越性。

在這些場景下使用node.js是非常合適的

  • web socket服務器
  • TCP/UDP套接字應用程序
  • 復雜邏輯的web應用
  • 命令行工具
  • 客戶端Javascript編譯器

 參考及最后

博客中基本理論知識都來源於《Node.js開發指南》,甚至很多篇幅都是直接使用原話,沒有抄襲據為己有的意思,只是作者說的太明白了,希望對初入node.js的朋友有幫助,當然如果感興趣可以直接購買原書。

希望嘮嘮叨叨這么多,能夠對對之前博客持有疑問的博友有所幫助,最后在嘮叨一句,單線程執行是指我們寫的JavaScript語句在同一時刻只能執行一句,而不是node.js是單線程,其實我們的異步I/O及事件循環都是另外線程在做。

 當然對於一些CPU密集的操作在node.js里面也有process.nextTick()這樣的解決方案或者直接使用C++處理,研究明白了和大家分享,也有人不滿意node.js的單線程寫了自己的module來讓node.js多線程,感興趣的同學可以看看node-threads-a-gogo

 由於剛剛接觸node.js,文中謬誤頗多,希望多家多多批評指教


免責聲明!

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



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