prepare是nodejs事件循环中的其中一个阶段(phase)。属于比较简单的一个阶段。我们知道libuv中分为handle和request。而prepare阶段的任务是属于handle。我们看一下他的定义。
![1c40d6e066b6e304d802e5d17933208a.png](https://i-blog.csdnimg.cn/blog_migrate/11e41421678af9c95864dbf74dd6d375.jpeg)
下面我们看看怎么使用它
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的结构体大概如下。
![33fffe17d319313483a1d25716dc4765.png](https://i-blog.csdnimg.cn/blog_migrate/4c329c33b2f63d07ed22a39abf150b8c.jpeg)
然后我们看看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阶段的过程。