本文探討了Node.js事件迴圈中的timers階段,分析了定時器的管理和執行過程。通過源碼解析,揭示了定時器超時檢查、回調執行以及定時器類型(setTimeout與setInterval)的內部實現機制。文章旨在幫助讀者理解Node.js中定時器的工作原理及其在事件驅動編程中的重要性。 ...
timers階段是Nodejs事件迴圈中的一個階段,這一階段主要是檢查是否有到期的定時器,如果有則執行其回調。
相關源碼位置:
timers
階段的代碼比較少,這裡直接貼出來,你也可以點進去上面的源碼看自己感興趣的部分:
void uv__run_timers(uv_loop_t* loop) {
struct heap_node* heap_node;
uv_timer_t* handle;
struct uv__queue* queue_node;
struct uv__queue ready_queue;
// 初始化一個空的 ready_queue 隊列,用於存放已經到期的定時器
uv__queue_init(&ready_queue);
for (;;) {
// 獲取堆中最小(即最早到期)的定時器節點
heap_node = heap_min(timer_heap(loop));
if (heap_node == NULL)
break; // 如果堆是空的(沒有定時器),則跳出迴圈
// 將堆節點轉換為 uv_timer_t 類型的定時器句柄 handle
handle = container_of(heap_node, uv_timer_t, node.heap);
if (handle->timeout > loop->time)
break; // 如果當前定時器的超時時間大於當前的迴圈時間,則跳出迴圈
// 停止到期的定時器,並將其插入到 ready_queue 隊列的尾部
uv_timer_stop(handle);
uv__queue_insert_tail(&ready_queue, &handle->node.queue);
}
// 處理 ready_queue 中的所有定時器
while (!uv__queue_empty(&ready_queue)) {
queue_node = uv__queue_head(&ready_queue); // 取出隊列頭部的節點
uv__queue_remove(queue_node); // 移除該節點
uv__queue_init(queue_node); // 重新初始化節點
handle = container_of(queue_node, uv_timer_t, node.queue); // 將節點轉換為定時器句柄 handle
// 重新啟動定時器,如果是重覆定時器,則根據設定的間隔重新計算超時時間
uv_timer_again(handle);
// 調用定時器的回調函數,執行定時器到期後的操作
handle->timer_cb(handle);
}
}
- 重新啟動定時器
uv_timer_again
的源碼位置:node/deps/uv/src/timer.c at main · nodejs/node (github.com)
內容比較少,這裡也直接貼出來:
int uv_timer_again(uv_timer_t* handle) {
if (handle->timer_cb == NULL)
return UV_EINVAL;
if (handle->repeat) {
uv_timer_stop(handle);
uv_timer_start(handle, handle->timer_cb, handle->repeat, handle->repeat);
}
return 0;
}
可以看到會檢查handle->repeat
,這裡其實就是setTimeout
和setInterval
的區別了,setInterval
到這裡會因為handle->repeat
為true
而重新開啟新的一輪計時,而setTimeout
則是直接跳過、結束了。
這裡的
handle->repeat
是uint64_t
類型,其實就是timeout
。
這裡順便貼一下uv_timer_start
的代碼,感興趣可以看看。
源碼位置:node/deps/uv/src/timer.c at main · nodejs/node (github.com)
int uv_timer_start(uv_timer_t* handle,
uv_timer_cb cb,
uint64_t timeout,
uint64_t repeat) {
uint64_t clamped_timeout;
// 如果定時器正在關閉或回調函數為 NULL,則返回錯誤代碼 UV_EINVAL
if (uv__is_closing(handle) || cb == NULL)
return UV_EINVAL;
// 停止定時器,以確保定時器在重新啟動前沒有在運行
uv_timer_stop(handle);
// 計算定時器的超時時間 clamped_timeout
// 它是當前事件迴圈時間 handle->loop->time 加上 timeout
clamped_timeout = handle->loop->time + timeout;
// 如果發生整數溢出,導致 clamped_timeout 小於 timeout,
// 則將 clamped_timeout 設置為最大值 UINT64_MAX
if (clamped_timeout < timeout)
clamped_timeout = (uint64_t) -1;
// 設置定時器的回調函數、超時時間和重覆間隔
handle->timer_cb = cb;
handle->timeout = clamped_timeout;
handle->repeat = repeat;
// start_id 用於在定時器比較函數 timer_less_than 中作為第二索引進行比較
// 並遞增事件迴圈的計時器計數器
handle->start_id = handle->loop->timer_counter++;
// 將定時器插入到事件迴圈的定時器堆中,並啟動定時器句柄
heap_insert(timer_heap(handle->loop),
(struct heap_node*) &handle->node.heap,
timer_less_than);
uv__handle_start(handle);
return 0;
}
-
clamped_timeout
和timeout
之間的關係:-
timeout
是傳遞給uv_timer_start
函數的參數,表示定時器的初始延遲時間,以毫秒為單位; -
clamped_timeout
是計算後的絕對時間,表示定時器實際超時的絕對時間點(以事件迴圈的時間handle->loop->time
為基準)。
-
-
uv_timer_start
的timeoute
參數 和 JS中setTimeout
的delay
參數是等價的。