nodejs事件循環階段之prepare
- 2020 年 3 月 12 日
- 筆記
prepare是nodejs事件循環中的其中一個階段(phase)。屬於比較簡單的一個階段。我們知道libuv中分為handle和request。而prepare階段的任務是屬於handle。我們看一下他的定義。
下面我們看看怎麼使用它
void prep_cb(uv_prepare_t *handle) { printf("Prep callbackn"); } int main() { uv_prepare_t prep; // uv_default_loop是libuv事件循環的核心結構體 uv_prepare_init(uv_default_loop(), &prep); uv_prepare_start(&prep, prep_cb); uv_run(uv_default_loop(), UV_RUN_DEFAULT); return 0; }
執行main函數,libuv就會在prepare階段執行回調prep_cb。我們分析一下這個過程。
int uv_prepare_init(uv_loop_t* loop, uv_prepare_t* handle) { uv__handle_init(loop, (uv_handle_t*)handle, UV_PREPARE); handle->prepare_cb = NULL; return 0; }
1 uv__handle_init是初始化libuv中handle的一個通用函數,他主要做了下面幾個事情。
1 初始化handle的類型,所屬loop 2 打上UV_HANDLE_REF,該標記影響事件循環的退出和poll io階段超時時間的計算。具體在start函數的時候分析。 3 handle插入loop->handle_queue隊列的隊尾,每個handle在init的時候都會插入libuv的handle隊列。
2 初始化prepare節點的回調。 init函數主要是做一些初始化操作。我們繼續要看start函數。
int uv_prepare_start(uv_prepare_t* handle, uv_prepare_cb cb) { // 如果已經執行過start函數則直接返回 if (uv__is_active(handle)) return 0; if (cb == NULL) return UV_EINVAL; QUEUE_INSERT_HEAD(&handle->loop->prepare_handles, &handle->queue); handle->prepare_cb = cb; uv__handle_start(handle); return 0; }
1 設置回調,把handle插入loop中的prepare_handles隊列,prepare_handles保存prepare階段的任務。在事件循環的prepare階段會逐個執行裏面的節點的回調。
2 設置UV_HANDLE_ACTIVE標記位,如果這handle還打了UV_HANDLE_REF標記(在init階段設置的),則事件循環中的活handle數加一。UV_HANDLE_ACTIVE標記這個handle是活的,影響事件循環的退出和poll io階段超時時間的計算。有活的handle的話,libuv如果運行在默認模式下,則不會退出,如果是其他模式,會退出。 執行完start函數,libuv的結構體大概如下。
然後我們看看libuv在事件循環的prepare階段是如何處理的。
void uv__run_prepare(uv_loop_t* loop) { uv_prepare_t* h; QUEUE queue; QUEUE* q; /* 把該類型對應的隊列中所有節點摘下來掛載到queue變量, 相當於清空prepare_handles隊列,因為如果直接遍歷prepare_handles隊列, 在執行回調的時候一直往prepare_handles隊列加節點,會導致下面的while循環無法退出。 先移除的話,新插入的節點在下一輪事件循環才會被處理。 */ QUEUE_MOVE(&loop->prepare_handles, &queue); // 遍歷隊列,執行每個節點裏面的函數 while (!QUEUE_EMPTY(&queue)) { // 取下當前待處理的節點,即隊列的頭 q = QUEUE_HEAD(&queue); // 取得該節點對應的整個結構體的基地址,即通過結構體成員取得結構體首地址 h = QUEUE_DATA(q, uv_prepare_t, queue); // 把該節點移出當前隊列 QUEUE_REMOVE(q); // 重新插入原來的隊列 QUEUE_INSERT_TAIL(&loop->prepare_handles, q); // 執行回調函數 h->prepare_cb(h); } }
run函數的邏輯很明了,就是逐個執行prepare_handles隊列的節點。我們回顧一開始的測試代碼。因為他設置了libuv的運行模式是默認模式。又因為有或者的handle(prepare節點),所以他是不會退出的。他會一直執行回調。那如果我們要退出怎麼辦呢?或者說不要執行prepare隊列的某個節點了。我們只需要stop一下就可以了。
int uv_prepare_stop(uv_prepare_t* handle) { if (!uv__is_active(handle)) return 0; // 把handle從prepare隊列中移除,但是還掛載到handle_queue中 QUEUE_REMOVE(&handle->queue); // 清除active標記位並且減去loop中handle的active數 uv__handle_stop(handle); return 0; }
stop函數和start函數是相反的作用,就不分析了。這就是nodejs中prepare階段的過程。