void uv_update_time(uv_loop_t* loop) {
/* 获取当前系统时间 */
DWORD ticks = GetTickCount();
/* The assumption is made that LARGE_INTEGER.QuadPart has the same type */
/* loop->time, which happens to be. Is there any way to assert this? */
LARGE_INTEGER* time = (LARGE_INTEGER*) &loop->time;
/* If the timer has wrapped, add 1 to it's high-order dword. */
/* uv_poll must make sure that the timer can never overflow more than */
/* once between two subsequent uv_update_time calls. */
if (ticks < time->LowPart) {
time->HighPart += 1;
}
time->LowPart = ticks;
}
该函数的内部实现,使用了 Windows 的 GetTickCount() 函数来设置当前时间。简单地来说,在调用setTimeout 函数之后,经过一系列的挣扎,内部的 timer->due 会被设置为当前 loop 的时间 + timeout。在 event loop 中,先通过 uv_update_time 更新当前 loop 的时间,然后在uv_process_timers 中检查是否有计时器到期,如果有就进入 JavaScript 的世界。通篇读下来,event loop大概是这样一个流程:
更新全局时间
检查定时器,如果有定时器过期,执行回调
检查 reqs 队列,执行正在等待的请求
进入 poll 函数,收集 IO 事件,如果有 IO 事件到来,将相应的处理函数添加到 reqs 队列中,以便在下一次 event loop 中执行。在 poll 函数内部,调用了一个系统方法来收集 IO 事件。这个方法会使得进程阻塞,直到有 IO 事件到来或者到达设定好的超时时间。调用这个方法时,超时时间设定为最近的一个 timer 到期的时间。意思就是阻塞收集 IO 事件,最大阻塞时间为 下一个 timer 的到底时间。
Windows下 poll 函数之一的源码:
复制代码 代码如下:
static void uv_poll(uv_loop_t* loop, int block) {
DWORD bytes, timeout;
ULONG_PTR key;
OVERLAPPED* overlapped;
uv_req_t* req;
if (block) {
/* 取出最近的一个计时器的过期时间 */
timeout = uv_get_poll_timeout(loop);
} else {
timeout = 0;
}
GetQueuedCompletionStatus(loop->iocp,
&bytes,
&key,
&overlapped,
/* 最多阻塞到下个计时器到期 */
timeout);
if (overlapped) {
/* Package was dequeued */
req = uv_overlapped_to_req(overlapped);
/* 把 IO 事件插入队列里 */
uv_insert_pending_req(loop, req);
} else if (GetLastError() != WAIT_TIMEOUT) {
/* Serious error */
uv_fatal_error(GetLastError(), "GetQueuedCompletionStatus");
}
}