本文為原創文章,出自http://cnodejs.org,轉載請註明出處和作者
作者:changlin
原文:http://cnodejs.org/blog/?p=244
nodejs的核心之一就是非阻塞的非同步IO,於是想知道它是怎麼實現的,挖了下nodejs源碼,找到些答案,在此跟大家分享下。首先,我用了一段js代碼test-fs-read.js做測試,代碼如下:
var path = require('path'), fs = require('fs'), filepath = path.join(__dirname, 'experiment.log'), fd = fs.openSync(filepath, 'r'); fs.read(fd, 12*1024*1024, 0, 'utf-8', function(err, str, bytesRead) { console.log('[main thread] execute read callback'); }); console.log('[main thread] execute operation after read');
這段代碼的非同步IO操作就在fs.read的調用上,讀取的experiment.log是一個12M的文字檔,所謂的非同步,大家大概能想得到運行時會先列印
[main thread] execute operation after read
然後列印回呼函數中的
[main thread] execute read callback
但 大家也許還聽說過,nodejs是單線程的,那又是怎麼實現非同步IO的呢?讀檔案操作是在哪裡執行的呢?讀完又是怎麼調用回呼函數的呢?猜想讀文 件可能是在另一個線程中完成的,讀完後通過事件通知nodejs執行回調。為了一探究竟,debug了一把nodejs和libeio源碼,重新編譯後, 運行測試代碼node test-fs-read.js,輸出如下:
可以看到,nodejs的IO操作是通過調用libeio庫完成的,debug從fs.read開始,js代碼經過v8編譯後,fs.read會調用node_file.cc中的Read方法,測試代碼的運行經曆了以下步驟:
1 node_file.cc中的Read方法調用libeio(eio.c)的eio_read, read請求被放入請求隊列req_queue中。
2 主線程建立了1個eio線程,此時主線程的read調用返回。
3 eio線程從req_queue中取出1個請求,開始執行read IO
4 主線程繼續執行read調用後的其它操作。
5 主線程poll eio,從響應隊列res_queue取已經完成的請求,此時res_queue為空白,主線程stop poll
6 eio線程完成了read IO,read請求被放入響應隊列res_queue中,並且向主線程發送libev事件want_poll(通過主線程初始化eio時提供的回呼函數)。
7 eio線程從req_queue中取下一個請求,此時已經沒有請求。
8 主線程響應want_poll事件,從res_queue中取出1個請求,取出請求後res_queue變為空白,主線程發送done_poll事件。
9 主線程執行請求的callback函數。
還需要說明的是,當同時有多個IO請求時,主線程會建立多個eio線程,以提高IO請求的處理速度。
為了更清晰的看到nodejs的IO執行過程,圖示如下,序號僅用來標示流程,與上述步驟序號並無對應關係。
最後總結幾條,不當之處還請大家指正。
1 nodejs通過libev事件得到IO執行狀態,而不是輪詢,提高了CPU利用率。
2 雖然nodejs是單線程的,但它的IO操作是多線程的,多個IO請求會建立多個libeio線程(最多4個),使通常情況的IO操作效能得到提高。
3 但是當IO操作情況比較複雜的時候,有可能造成線程競爭狀態,導致IO效能降低;而且libeio最多建立4個線程,當同時有大量IO請求時,實際效能有 待測量。另外,由於每個IO請求對應一個libeio的資料結構,當同時有大量IO操作駐留在系統中時候,會增加記憶體開銷。
4 Libeio為了實現非同步IO功能,帶來了額外的管理,當IO資料量比較小的時候,整體效能不一定比同步IO好。
1、node使用libeio實現非阻塞非同步檔案操作,但實際上libeio是通過多線程的方式,在標準的阻塞式IO上類比非阻塞非同步。
2、為什麼node沒有使用原生態的非同步IO API(AIO)?原因可能是為了實現跨平台的相容性(主要針對Windows,具體參見:http://groups.google.com /group/nodejs/browse_thread/thread/aff97d25c59f6f2d)
3、libeio的4個線程限制是預設配置,可以對此配置進行修改