libuv的定时器原理源码解析

2019-03-15 16:28:07 浏览数 (2)

首先我们看一下timer的结构体

代码语言:javascript复制
struct uv_timer_s {
    void* data;    
    uv_loop_t* loop;
    uv_handle_type type;
    uv_close_cb close_cb;
    void* handle_queue[2]; 
    union {      
       int fd;
       void* reserved[4]; 
    } u;  
    uv_handle_t* next_closing; 
    unsigned int flags;
    uv_timer_cb timer_cb;
    void* heap_node[3]; 
    uint64_t timeout; 
    uint64_t repeat; 
    uint64_t start_id;
}

如果需要使用定时器,首先要对定时器的结构体进行初始化。

代码语言:javascript复制
#if defined(_WIN32)
# define uv__handle_platform_init(h) ((h)->u.fd = -1)
#else
# define uv__handle_platform_init(h) ((h)->next_closing = NULL)
#endif

#define uv__handle_init(loop_, h, type_)                                      
  do {                                                                        
    (h)->loop = (loop_);                                                      
    (h)->type = (type_);                                                      
    (h)->flags = UV_HANDLE_REF;  /* Ref the loop when active. */              
    QUEUE_INSERT_TAIL(&(loop_)->handle_queue, &(h)->handle_queue);            
    uv__handle_platform_init(h);                                              
  }                                                                           
  while (0)
// 初始化uv_timer_t结构体
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;
}

接着我们需要启动一个定时器。

代码语言:javascript复制
// 启动一个计时器
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;
  // 重新执行start的时候先把之前的停掉
  if (uv__is_active(handle))
    uv_timer_stop(handle);
  // 超时时间,为绝对值
  clamped_timeout = handle->loop->time   timeout;
  if (clamped_timeout < timeout)
    clamped_timeout = (uint64_t) -1;
  // 初始化回调,超时时间,是否重复计时,赋予一个独立无二的id
  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  ;
  // 插入最小堆
  heap_insert(timer_heap(handle->loop),
              (struct heap_node*) &handle->heap_node,
              timer_less_than);
  // 激活该handle
  uv__handle_start(handle);

  return 0;
}

这时候的内存视图是。

在这里插入图片描述 在uv_run的时候会执行过期的定时器。

代码语言:javascript复制
// 找出已经超时的节点,并且执行里面的回调
void uv__run_timers(uv_loop_t* loop) {
  struct heap_node* heap_node;
  uv_timer_t* handle;

  for (;;) {
    heap_node = heap_min(timer_heap(loop));
    if (heap_node == NULL)
      break;

    handle = container_of(heap_node, uv_timer_t, heap_node);
    // 如果当前节点的时间大于当前时间则返回,说明后面的节点也没有超时
    if (handle->timeout > loop->time)
      break;
    // 移除该计时器节点,重新插入最小堆,如果设置了repeat的话
    uv_timer_stop(handle);
    uv_timer_again(handle);
    // 执行超时回调
    handle->timer_cb(handle);
  }
}

执行定时器的时候首先会先移除该定时器,然后如果设置了repeat的话,再次加入到最小堆里,最后执行超时回调。这里有个需要注意的是设置了repeat的定时器,意思是timeout时间后触发第一次超时,后面每隔repeat的时间,触发一次超时。

代码语言:javascript复制
#define uv__handle_stop(h)                                                    
  do {                                                                        
    if (((h)->flags & UV_HANDLE_ACTIVE) == 0) break;                          
    (h)->flags &= ~UV_HANDLE_ACTIVE;                                          
    if (((h)->flags & UV_HANDLE_REF) != 0) uv__active_handle_rm(h);           
  }                                                                           
  while (0)
// 停止一个计时器
int uv_timer_stop(uv_timer_t* handle) {
  if (!uv__is_active(handle))
    return 0;
  // 从最小堆中移除该计时器节点
  heap_remove(timer_heap(handle->loop),
              (struct heap_node*) &handle->heap_node,
              timer_less_than);
  // 清除激活状态和handle的active数减一
  uv__handle_stop(handle);

  return 0;
}

// 重新启动一个计时器,需要设置repeat标记 
int uv_timer_again(uv_timer_t* handle) {
  if (handle->timer_cb == NULL)
    return UV_EINVAL;
  // 如果设置了repeat标记说明计时器是需要重复触发的
  if (handle->repeat) {
    // 先把旧的计时器节点从最小堆中移除,然后再重新开启一个计时器
    uv_timer_stop(handle);
    uv_timer_start(handle, handle->timer_cb, handle->repeat, handle->repeat);
  }

  return 0;
}

至此,一个定时器的声明周期结束。loop也恢复初始状态。

0 人点赞