本篇文章主要对
uv_timer_t
结构体进行展开介绍
uv_timer_t声明
直接从uv.h切入,很容易就能找到uv_timer_s
结构体声明:
struct uv_timer_s {
UV_HANDLE_FIELDS
UV_TIMER_PRIVATE_FIELDS
};
其中UV_HANDLE_FIELDS
为所有句柄的抽象基础宏,在上一篇文章介绍过,在这里不做过多介绍。我们主要看一下timer的私有宏UV_TIMER_PRIVATE_FIELDS
,由于平台的差异,我们在这里只对unix.h中的timer私有宏进行介绍:
#define UV_TIMER_PRIVATE_FIELDS \
uv_timer_cb timer_cb; \
void* heap_node[3]; \
uint64_t timeout; \
uint64_t repeat; \
uint64_t start_id;
下面是结构体内部数据结构的介绍:
timer_cb
定时器的回调函数heap_node
二叉堆的指针,分别指向left、right和parenttimeout
过期时间,在timeout
毫秒之后,指定的timer_cb
会被调用repeat
间隔时间,回调函数在第一次timeout
毫秒调用之后会间隔repeat
毫秒进行调用start_id
定时器启动时候注册的id,如果定时器的触发时间是一致的,会对比start_id
来判别先后顺序
timer相关API
uv_timer_init
视线挪到timer.c中:
int uv_timer_init(uv_loop_t* loop, uv_timer_t* handle) {
uv__handle_init(loop, (uv_handle_t*)handle, UV_TIMER);
handle->timer_cb = NULL;
handle->repeat = 0;
return 0;
}
对基础句柄进行初始化,并设定句柄类型(h-> type)为UV_TIMER
从而实现对定时器句柄的初始化。
uv_timer_start
int uv_timer_start(uv_timer_t* handle,
uv_timer_cb cb,
uint64_t timeout,
uint64_t repeat) {
uint64_t clamped_timeout;
if (cb == NULL)
return UV_EINVAL;
if (uv__is_active(handle))
uv_timer_stop(handle);
clamped_timeout = handle->loop->time + timeout; //用loop的当前时间加上过期时间为真正的调用时间
if (clamped_timeout < timeout)
clamped_timeout = (uint64_t) -1; // 如果真正的调用时间要小于过期时间则设置调用时间为最大数值
handle->timer_cb = cb;
handle->timeout = clamped_timeout;
handle->repeat = repeat;
/* start_id is the second index to be compared in uv__timer_cmp() */
handle->start_id = handle->loop->timer_counter++; //设定此timer的唯一id
heap_insert(timer_heap(handle->loop),
(struct heap_node*) &handle->heap_node,
timer_less_than); //二叉堆操作(最小堆,可以快速取出过期时间最短的timer句柄)
uv__handle_start(handle); // 向loop中注册活动句柄
return 0;
}
在uv_timer_start
中,分别对timer的回调函数、过期时间、间隔时间进行了设定。在这里简单介绍一下为什么会有如下这个判断:
if (clamped_timeout < timeout)
clamped_timeout = (uint64_t) -1;
libuv会每次事件循环的伊始通过uv_update_time()
来更新handle->loop->time
。
但是如果调用方在独立使用uv_timer_start
的时候,忘记先调用uv_update_time()
则会造成如上这种判断的情况。而libuv开发者没有把uv_update_time()
写入到uv_timer_start
中则是为了保证libuv时间循环的性能。
uv__run_timers
此函数供event-loop调用,其中有一点需要注意的地方:
void uv__run_timers(uv_loop_t* loop) {
struct heap_node* heap_node;
uv_timer_t* handle;
for (;;) {
// ...
uv_timer_stop(handle);
uv_timer_again(handle);
handle->timer_cb(handle);
}
}
uv_timer_stop
和uv_timer_again
在这里不做过多介绍,通过函数名就能大概明白具体是做什么的。而为何要在timer回调运行之前对timer进行停止和下一次调用的注册,则是为了防止handle->timer_cb
回调阻塞时间过长,导致每次uv_timer_again
会逐渐增大repeat timers
回调触发的时间。
node中对uv_timer_t的应用
由于之前在《node源码粗读》系列对Timers API进行过详尽的分析,所以在这里主要是借对uv_timer_t
的分析串一下,在node中的调用函数如下:
int err = uv_timer_start(&wrap->handle_, OnTimeout, timeout, 0);
// timer_cb回调函数OnTimeout如下
static void OnTimeout(uv_timer_t* handle) {
TimerWrap* wrap = static_cast<TimerWrap*>(handle->data);
Environment* env = wrap->env();
HandleScope handle_scope(env->isolate());
Context::Scope context_scope(env->context());
wrap->MakeCallback(kOnTimeout, 0, nullptr);
}
通过如上代码可以得知,node的上下文环境,全部是通过uv_handle_t
中的handle->data
传递到timer_cb
中的,回调最终在刚才所讲到的uv__run_timers
中的handle->timer_cb(handle);
触发运行。
原文地址:https://github.com/xtx1130/blog/issues/31,如果文中描述有问题,还请大神留言斧正。 by 小菜
我大node的牛人真多啊