libuv源码粗读(3):uv_timer_t定时器句柄结构体介绍
发布于 6 年前 作者 xtx1130 4049 次浏览 来自 分享

本篇文章主要对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和parent
  • timeout 过期时间,在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_stopuv_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 小菜

1 回复

我大node的牛人真多啊

回到顶部