今天繼續看看 event loop 的核心循環, uv_run() , 可以查看以下網址
https://github.com/nodejs/node/blob/e46c680bf2b211bbd52cf959ca17ee98c7f657f5/deps/uv/src/win/core.c
int uv_run(uv_loop_t *loop, uv_run_mode mode) {
DWORD timeout;
int r;
int ran_pending;
r = uv__loop_alive(loop);
if (!r)
uv_update_time(loop);
while (r != 0 && loop->stop_flag == 0) {
uv_update_time(loop);
uv__run_timers(loop);
ran_pending = uv_process_reqs(loop);
uv_idle_invoke(loop);
uv_prepare_invoke(loop);
timeout = 0;
if ((mode == UV_RUN_ONCE && !ran_pending) || mode == UV_RUN_DEFAULT)
timeout = uv_backend_timeout(loop);
if (pGetQueuedCompletionStatusEx)
uv__poll(loop, timeout);
else
uv__poll_wine(loop, timeout);
/* Run one final update on the provider_idle_time in case uv__poll*
* returned because the timeout expired, but no events were received. This
* call will be ignored if the provider_entry_time was either never set (if
* the timeout == 0) or was already updated b/c an event was received.
*/
uv__metrics_update_idle_time(loop);
uv_check_invoke(loop);
uv_process_endgames(loop);
if (mode == UV_RUN_ONCE) {
/* UV_RUN_ONCE implies forward progress: at least one callback must have
* been invoked when it returns. uv__io_poll() can return without doing
* I/O (meaning: no callbacks) when its timeout expires - which means we
* have pending timers that satisfy the forward progress constraint.
*
* UV_RUN_NOWAIT makes no guarantees about progress so it's omitted from
* the check.
*/
uv__run_timers(loop);
}
r = uv__loop_alive(loop);
if (mode == UV_RUN_ONCE || mode == UV_RUN_NOWAIT)
break;
}
/* The if statement lets the compiler compile it to a conditional store.
* Avoids dirtying a cache line.
*/
if (loop->stop_flag != 0)
loop->stop_flag = 0;
return r;
}
原則上這個循環就是不斷的在處理任務, 可以稱得上是 node.js 背後默默做事的工具人了。
今天我們先看
uv_update_time(loop);
uv__run_timers(loop);
這兩句
void uv_update_time(uv_loop_t* loop) {
uint64_t new_time = uv__hrtime(1000);
assert(new_time >= loop->time);
loop->time = new_time;
}
主要是利用 uv__hrtime 取得新的時間
static void uv__hrtime_init_once(void) {
if (KERN_SUCCESS != mach_timebase_info(&timebase))
abort();
time_func = (uint64_t (*)(void)) dlsym(RTLD_DEFAULT, "mach_continuous_time");
if (time_func == NULL)
time_func = mach_absolute_time;
}
uint64_t uv__hrtime(uv_clocktype_t type) {
uv_once(&once, uv__hrtime_init_once);
return time_func() * timebase.numer / timebase.denom;
}
uv__hrtime 主要就是調用一些 OS API 取得當下時間
之所以不每次要時間都和 OS 要, 是為了避免 , system call 會阻塞運作, 所以盡量都維持在 user mode
重點環節, 提供註釋
void uv__run_timers(uv_loop_t* loop) {
// 取得 heap 首節點
struct heap_node* heap_node;
uv_timer_t* handle;
for (;;) {
// 取得 heap 中最小節點
heap_node = heap_min(timer_heap(loop));
// heap 為空則斷開
if (heap_node == NULL)
break;
// 當 最小節點也大於當前時間, 就退出
handle = container_of(heap_node, uv_timer_t, heap_node);
if (handle->timeout > loop->time)
break;
// 刪除節點
uv_timer_stop(handle);
// 如果是重複任務, 就再插回 heap
uv_timer_again(handle);
// 執行節點任務
handle->timer_cb(handle);
}
}
這句方法主要是用來處理需要定期觸發的任務, 可以看到 node 維護了一個 heap ( 最小樹 ) , 當有以時間為觸發基準的任務被註冊後, 就包成節點放入 heap , 節點中包含事件觸發時間及其回調函數。
因為最小樹的頂端永遠是最小節點 (在這裡可以視為觸發時間最早者) , 所以可以用最快的速度鎖定要處理的任務。 以下簡述流程。
明天我們來看看 , uv__io_poll(loop, timeout) ,中間會先跳過一些階段, 希望藉此可以在看其他階段前有更宏觀的視野。
明天見 !