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階段的過程。