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阶段的过程。