Node.js? is a JavaScript runtime built on Chrome's V8 JavaScript engine. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient.
Node.js官網(wǎng)上的介紹铣墨,其中事件驅(qū)動非阻塞I/O模型是被大家所津津樂道的髓梅,但是有多少人真正了解其究竟呢?有人可能會想到libuv稀余,沒錯,libuv確實是其幕后英雄迟几。那么問題又來了敌厘,到底是怎么用libuv實現(xiàn)的呢?下面我們來一探究竟虐骑。
libuv當(dāng)初主要就是為Node.js開發(fā)的,提供跨平臺的事件驅(qū)動異步I/O能力赎线,當(dāng)然現(xiàn)在肯定不僅限于Node.js使用廷没。我們先來看一下libuv的Design overview。
從架構(gòu)圖上看垂寥,libuv是對多個平臺上的事件驅(qū)動異步I/O庫進行了封裝颠黎,如Linux下的epoll另锋、FreeBSD下的kqueue、Solaris下的event ports狭归、Windows下的IOCP夭坪。
上圖所描述的事件循環(huán)是libuv中最重要的概念,其中的Poll for I/O
就是事件驅(qū)動異步I/O能力的核心过椎。到這里我們有必要先了解一些基礎(chǔ)知識室梅,Linux IO模式及 select、poll疚宇、epoll詳解亡鼠,否則后面的東西就不是特別好理解了。
正題
經(jīng)過前面的學(xué)習(xí)灰嫉,應(yīng)該對libuv有了一個整體的印象拆宛,總結(jié)一下嗓奢, libuv其實就是把各種handle
和io_watcher
放到事件循環(huán)里讼撒,然后每一次循環(huán)都去檢查一下是否有他們關(guān)心的事件需要處理,有則調(diào)用相應(yīng)的callback
股耽,沒有則繼續(xù)循環(huán)根盒。要想弄清楚Node.js之異步那些事,我們需要關(guān)心的是物蝙,Node.js如何運行事件循環(huán)炎滞,何時把handle
和io_watcher
放入事件循環(huán),以及如何調(diào)用相應(yīng)的callback
诬乞。
開始之前册赛,本次分析的代碼版本為Node.js v0.12.6,Linux平臺震嫉。
Run
node.cc
中Start
方法運行事件循環(huán)森瘪,精華部分如下。唯一有些特別的地方就是票堵,在一個while
循環(huán)中包了兩個uv_run
扼睬,模式分別是UV_RUN_ONCE
和UV_RUN_NOWAIT
,其原因在中間的兩行注釋中已經(jīng)說得很明白了悴势。
...
bool more;
do {
more = uv_run(env->event_loop(), UV_RUN_ONCE);
if (more == false) {
EmitBeforeExit(env);
// Emit `beforeExit` if the loop became alive either after emitting
// event, or after running some callbacks.
more = uv_loop_alive(env->event_loop());
if (uv_run(env->event_loop(), UV_RUN_NOWAIT) != 0)
more = true;
}
} while (more == true);
...
然后我們可以看看core.c
中uv_run
方法的代碼窗宇,跟上面事件循環(huán)的流程圖是可以一一對應(yīng)的。
Data Structure
繼續(xù)看代碼之前特纤,有必要先了解一下重要的數(shù)據(jù)結(jié)構(gòu)和相互的關(guān)系军俊,以便更好的理解。
io_watcher
接著我之前文章Node.js之HelloWorld背后的大坑的思路捧存,還拿Hello World舉例子粪躬,跟libuv有關(guān)的代碼都在tcp_warp.cc
里面了官硝。
TCPWrap::New
stream.c
中uv__stream_init
方法有如下代碼,將io_watcher
的cb
設(shè)置為uv__stream_io
短蜕,fd
設(shè)置為-1
氢架,這里只是在stream層面做的初始化設(shè)置,后面到tcp層面還會有相應(yīng)的改變朋魔。
uv__io_init(&stream->io_watcher, uv__stream_io, -1);
TCPWrap::Bind
tcp.c
的maybe_new_socket
方法中岖研,uv__socket
方法生成了新的fd
,uv__stream_open
方法將其設(shè)置到io_watcher
的fd
警检。
TCPWrap::Listen
tcp.c
的uv_tcp_listen
方法中有如下代碼孙援,將io_watcher
的cb
設(shè)置為uv__server_io
,uv__server_io
里面會調(diào)用connection_cb
扇雕,connection_cb
已經(jīng)被設(shè)置為cb
拓售,而這個cb
正是tcp_wrap.cc
中的TCPWrap::OnConnection
方法。
...
tcp->connection_cb = cb;
/* Start listening for connections. */
tcp->io_watcher.cb = uv__server_io;
uv__io_start(tcp->loop, &tcp->io_watcher, UV__POLLIN);
...
core.c
中uv__io_start
方法有如下代碼镶奉,利用void* watcher_queue[2]
變量將io_watcher
加入到uv_loop_t
的隊列中去础淤,具體操作詳見queue.h
。將uv_loop_t
的uv__io_t** watchers
當(dāng)做數(shù)組使用哨苛,fd
為下標(biāo)鸽凶,io_watcher
為對應(yīng)的值。
...
if (QUEUE_EMPTY(&w->watcher_queue))
QUEUE_INSERT_TAIL(&loop->watcher_queue, &w->watcher_queue);
if (loop->watchers[w->fd] == NULL) {
loop->watchers[w->fd] = w;
loop->nfds++;
}
...
uv__io_poll
linux-core.c
中的uv__io_poll
方法建峭,一行一行的讀就可以了玻侥,前面的鋪墊已經(jīng)做得很充分了,只要讀懂謎底便可揭曉亿蒸。
未完
- 接下來我們來說說
process.nextTick(callback)
的事凑兰,在node.js
中定義如下,把callback
放到了nextTickQueue
隊列中边锁,那么Node.js是在什么時候消費這個隊列的呢姑食?
function nextTick(callback) {
// on the way out, don't bother. it won't get fired anyway.
if (process._exiting)
return;
var obj = {
callback: callback,
domain: process.domain || null
};
nextTickQueue.push(obj);
tickInfo[kLength]++;
}
-
tcp_wrap.cc
中TCPWrap::OnConnection
方法有如下代碼,MakeCallback
方法的出處如下圖砚蓬。
tcp_wrap->MakeCallback(env->onconnection_string(), ARRAY_SIZE(argv), argv);
-
async-wrap.cc
中MakeCallback
方法有如下代碼矢门。
env()->tick_callback_function()->Call(process, 0, NULL);
-
node.cc
中SetupNextTick
方法有如下代碼,對tick_callback_function()
進行了設(shè)定灰蛙。
env->set_tick_callback_function(args[1].As<Function>());
-
node.cc
中SetupProcessObject
方法有如下代碼祟剔,SetupNextTick
被設(shè)定為process
中的_setupNextTick
方法。
NODE_SET_METHOD(process, "_setupNextTick", SetupNextTick);
-
node.js
中startup.processNextTick
方法有如下代碼摩梧。
process._setupNextTick(tickInfo, _tickCallback, _runMicrotasks);
-
node.js
中_tickCallback
方法代碼如下物延,消費nextTickQueue
隊列中的callback
方法。
function _tickCallback() {
var callback, threw, tock;
scheduleMicrotasks();
while (tickInfo[kIndex] < tickInfo[kLength]) {
tock = nextTickQueue[tickInfo[kIndex]++];
callback = tock.callback;
threw = true;
try {
callback();
threw = false;
} finally {
if (threw)
tickDone();
}
if (1e4 < tickInfo[kIndex])
tickDone();
}
tickDone();
}
省略去中間步驟仅父,實際上是產(chǎn)生了如下的調(diào)用關(guān)系叛薯。
TCPWrap::OnConnection()
↓↓↓
_tickCallback()
總結(jié)
簡單說浑吟,整個過程是這樣的,事件循環(huán)中有相應(yīng)I/O事件發(fā)生的時候耗溜,libuv調(diào)用Node.js C++部分的回調(diào)组力,C++部分調(diào)用JavaScript部分的回調(diào),順便調(diào)用nextTick設(shè)定的回調(diào)抖拴。
還是認(rèn)真讀代碼吧燎字,以上寫的僅供參考。