libuv 句柄解读-准备(prepare)
prepare handle 可以被译为准备句柄,如果程序中启动了 prepare handle 后,那么它在每次事件循环的时候都会被执行一遍,并且在 I/O 轮询之前被执行,注意,虽然它的原理与 idle handle 差不多,但是还是有一些差别的。
解读
prepare handle可以被译为准备句柄,如果程序中启动了prepare handle后,那么它在每次事件循环的时候都会被执行一遍,并且在 I/O 轮询之前被执行,注意,虽然它的原理与 idle handle 差不多,但是还是有一些差别的。
回顾上一篇文章,idle 句柄在每次循环迭代中运行一次给定的回调,而且执行顺序是在prepare handle之前。它与 prepare 句柄的显著区别在于:当存在活动的空闲句柄时,循环将执行零超时轮询,而不是阻塞 I/O,idle 句柄的回调一般用来执行一些低优先级的任务,如果没有 idle handle,那么事件循环将进入 I/O 的阻塞中。
我为什么在上一节不讲清楚这个原理呢,原因有两点,一开始我没有注意到,在写这篇文章的时候我注意到了源码,第二点原因是我想放在这里形成对比,产生的结果应该更明显,能让大家区分 idle 与 prepare handle,其实在计算阻塞时间的函数 uv_backend_timeout() 中,我们可 以看到一句话
if (!QUEUE_EMPTY(&loop->idle_handles))
return 0;
这意味着当还存在 idle handle 处于活跃状态时,事件循环将不会进入阻塞状态的,或者说循环将执行零超时轮询。
int uv_backend_timeout(const uv_loop_t* loop) {
if (loop->stop_flag != 0)
return 0;
if (!uv__has_active_handles(loop) && !uv__has_active_reqs(loop))
return 0;
if (!QUEUE_EMPTY(&loop->idle_handles))
return 0;
if (loop->closing_handles)
return 0;
return uv__next_timeout(loop);
}
回顾一下 libuv 的事件循环过程,它有一个uv__run_prepare()函数会被执行,就是在事件循环迭代的过程中处理prepare handle。
总之你可以理解为:idle 与prepare handle几乎是一样的东西,只不过产生他们的 handle 处于活跃状态的时候对事件循环会产生不同的影响,仅此而已。
数据类型
uv_prepare_t 是prepare handle的数据类型,通过它可以定义一个prepare handle的实例。
typedef struct uv_prepare_s uv_prepare_t;
prepare handle的回调函数
typedef void (*uv_prepare_cb)(uv_prepare_t* handle);
如果prepare handle的实例想要执行回调函数,则需要传递一个uv_prepare_cb类型的回调函数到uv_prepare_start()函数中。
API
- 初始化句柄。
int uv_prepare_init(uv_loop_t* loop, uv_prepare_t* prepare)
- 以给定的回调函数开始句柄。
int uv_prepare_start(uv_prepare_t* prepare, uv_prepare_cb cb)
- 停止句柄,回调函数将不会再被调用。
int uv_prepare_stop(uv_prepare_t* prepare)
样例解读
说了那么多,首先方个prepare handle的例子吧,通过例子去讲解prepare handle相关的知识。
#include <stdio.h>
#include <stdlib.h>
#include <uv.h>
int64_t num = 0;
void my_idle_cb(uv_idle_t* handle)
{
num++;
printf("idle callback\n");
if (num >= 5) {
printf("idle stop, num = %ld\n", num);
uv_stop(uv_default_loop());
}
}
void my_prep_cb(uv_prepare_t *handle)
{
printf("prep callback\n");
}
int main()
{
uv_idle_t idler;
uv_prepare_t prep;
uv_idle_init(uv_default_loop(), &idler);
uv_idle_start(&idler, my_idle_cb);
uv_prepare_init(uv_default_loop(), &prep);
uv_prepare_start(&prep, my_prep_cb);
uv_run(uv_default_loop(), UV_RUN_DEFAULT);
return 0;
}
main 函数的处理 过程:
- 定义 idler 实例。
- 定义 prep 实例。
- 初始化 idler 实例。
- 初始化 prep 实例。
- 启动 idler 实例,并传入对应的回调函数
my_idler_cb。 - 启动 prep 实例,并传入对应的回调函数
my_prep_cb。 - 启动事件循环。
- 在结束后退出。
my_prep_cb回调函数的处理:
- 打印相关的信息
my_idle_cb回调函数的处理:
- 在每次调用回调函数的时候,对全局变量计数。
在计数值达到5后,停止事件循环uv_stop()`。
其实你如果直接全局搜索 uv_prepare_init 这个函数的话,是找不到它的,因为 libuv 做了很骚的操作,将 prepare、prepare 以及 check 相关的函数都通过 C 语言的##连接符统一用宏定义了,并且在编译器预处理的时候产生对应的函数代码,具体源码如下:
src\unix\loop-watcher.c文件内容
#include "uv.h"
#include "internal.h"
#define UV_LOOP_WATCHER_DEFINE(name, type) \
int uv_##name##_init(uv_loop_t* loop, uv_##name##_t* handle) { \
uv__handle_init(loop, (uv_handle_t*)handle, UV_##type); \
handle->name##_cb = NULL; \
return 0; \
} \
\
int uv_##name##_start(uv_##name##_t* handle, uv_##name##_cb cb) { \
if (uv__is_active(handle)) return 0; \
if (cb == NULL) return UV_EINVAL; \
QUEUE_INSERT_HEAD(&handle->loop->name##_handles, &handle->queue); \
handle->name##_cb = cb; \
uv__handle_start(handle); \
return 0; \
} \
\
int uv_##name##_stop(uv_##name##_t* handle) { \
if (!uv__is_active(handle)) return 0; \
QUEUE_REMOVE(&handle->queue); \
uv__handle_stop(handle); \
return 0; \
} \
\
void uv__run_##name(uv_loop_t* loop) { \
uv_##name##_t* h; \
QUEUE queue; \
QUEUE* q; \
QUEUE_MOVE(&loop->name##_handles, &queue); \
while (!QUEUE_EMPTY(&queue)) { \
q = QUEUE_HEAD(&queue); \
h = QUEUE_DATA(q, uv_##name##_t, queue); \
QUEUE_REMOVE(q); \
QUEUE_INSERT_TAIL(&loop->name##_handles, q); \
h->name##_cb(h); \
} \
} \
\
void uv__##name##_close(uv_##name##_t* handle) { \
uv_##name##_stop(handle); \
}
UV_LOOP_WATCHER_DEFINE(prepare, PREPARE)
UV_LOOP_WATCHER_DEFINE(check, CHECK)
UV_LOOP_WATCHER_DEFINE(idle, IDLE)
它利用宏定义,在预处理阶段拓展成三个不同类型,但是处理逻辑一样的代码。有三种类型,分别是 prepare,check,prepare。
如果你将代码中的##name或者name##或 者##name##替换为prepare,##type替换为PREPARE,就可以得到以下的代码:
- 这就是编译器预处理生成的
prepare handle相关的代码:
int uv_prepare_init(uv_loop_t* loop, uv_prepare_t* handle) {
uv__handle_init(loop, (uv_handle_t*)handle, UV_IDLE);
handle->prepare_cb = NULL;
return 0;
}
int uv_prepare_start(uv_prepare_t* handle, uv_prepare_cb cb) {
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;
}
int uv_prepare_stop(uv_prepare_t* handle) {
if (!uv__is_active(handle)) return 0;
QUEUE_REMOVE(&handle->queue);
uv__handle_stop(handle);
return 0;
}
void uv__run_prepare(uv_loop_t* loop) {
uv_prepare_t* h;
QUEUE queue;
QUEUE* q;
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);
}
}
void uv__prepare_close(uv_prepare_t* handle) {
uv_prepare_stop(handle);
}