libuv学习笔记(5)

libuv学习笔记(5)

uv_timer_t 定时器的数据结构与相关API

所有的定时器存在一颗红黑树中,红黑树在tree.h中定义,通过宏的方式实现类似模板的功能。红黑树的具体实现可以参考sgi_stl的红黑树,差别不大,树节点的内存分配需要用户管理。

数据结构

在uv.h中定义

typedef struct uv_timer_s uv_timer_t;
struct uv_timer_s 
{
  UV_HANDLE_FIELDS//uv_handle_t的数据,此处不再展开,请参考之前的文章
  //UV_TIMER_PRIVATE_FIELDS展开如下:
  RB_ENTRY(uv_timer_s) tree_entry;  //红黑树节点                                          
  uint64_t due;                                                              
  uint64_t repeat;  //重复执行的间隔,为零将只执行一次回调                                                          
  uint64_t start_id;                                                          
  uv_timer_cb timer_cb;//回调函数
};

相关函数

1.初始化函数,导出函数,在uv.h中声明,timer.c中定义
int uv_timer_init(uv_loop_t* loop, uv_timer_t* handle) 
{
  //uv__handle_init(loop, (uv_handle_t*) handle, UV_TIMER);宏展开如下:
  do 
  {                                                                        
    (handle)->loop = (loop);                                                     
    (handle)->type = (UV_TIMER);                                                     
    (handle)->flags = UV__HANDLE_REF;  /* 设置为引用状态 */           
    //将定时器handle添加到loop  handle队列的末尾
    QUEUE_INSERT_TAIL(&(loop)->handle_queue, &(handle)->handle_queue);               
    //uv__handle_platform_init(h); 宏展开如下:
    ((handle)->u.fd = -1)                                            
  }                                                                          
  while (0);
  handle->timer_cb = NULL;
  handle->repeat = 0;
  return 0;
}
2.开启定时器,导出函数,在uv.h中声明,timer.c中定义
int uv_timer_start(uv_timer_t* handle,//定时器句柄
                   uv_timer_cb cb,//定时器回调函数
                   uint64_t timeout,//第一次回调间隔,毫秒
                   uint64_t repeat)//重复回调间隔,毫秒
{
  uv_loop_t* loop = handle->loop;
  uv_timer_t* old;

  if (timer_cb == NULL)//必须设置回调函数
    return UV_EINVAL;
  //如果已经是激活状态,先停止。此时起到更新作用
  //if (uv__is_active(handle))展开如下:
  if ((((handle)->flags & UV__HANDLE_ACTIVE) != 0))
    uv_timer_stop(handle);

  handle->timer_cb = timer_cb;
  //获取第一次执行相对loop的当前时间的时间。(loop->time + timeout)
  handle->due = get_clamped_due_time(loop->time, timeout);
  handle->repeat = repeat;
  //uv__handle_start(handle);展开如下:
  //激活定时器
  do {                                                                        
    assert(((handle)->flags & UV__HANDLE_CLOSING) == 0);                           
    if (((handle)->flags & UV__HANDLE_ACTIVE) != 0) break;                         
    (handle)->flags |= UV__HANDLE_ACTIVE;                                          
    if (((handle)->flags & UV__HANDLE_REF) != 0) 
        //uv__active_handle_add(handle); 展开如下:    
        do 
        {                                                                       
            (h)->loop->active_handles++;//loop中活动的句柄数量加一                                            
        }                                                                           
        while (0)  
  }                                                                           
  while (0);

  // start_id 是uv__timer_cmp()函数中的第二比较对象
  handle->start_id = handle->loop->timer_counter++;//定时器数量加一,timer_counter只会递增
  //将定时器插入红黑树(树中不应该存在相同的定时器——时间相同,strat_id也相同)
  old = RB_INSERT(uv_timer_tree_s, &loop->timers, handle);
  assert(old == NULL);

  return 0;
}
3.停止定时器,导出函数,在uv.h中声明,timer.c中定义
int uv_timer_stop(uv_timer_t* handle) 
{
  //非线程安全
  uv_loop_t* loop = handle->loop;
  //非激活状态,直接返回
  if (!uv__is_active(handle))
    return 0;
  //在红黑树中移除
  RB_REMOVE(uv_timer_tree_s, &loop->timers, handle);
  //uv__handle_stop(handle);展开:
  do {                                                                        
    assert(((h)->flags & UV__HANDLE_CLOSING) == 0);
    //非活动状态直接返回                           
    if (((h)->flags & UV__HANDLE_ACTIVE) == 0) break;                         
    (h)->flags &= ~UV__HANDLE_ACTIVE;//去掉激活状态标志     
    //如果定时器处于引用状态,loop活动的handle数减一                                             
    if (((h)->flags & UV__HANDLE_REF) != 0) uv__active_handle_rm(h); 
  }                                                                           
  while (0)return 0;
}
4.再次运行,导出函数,在uv.h中声明,timer.c中定义
int uv_timer_again(uv_timer_t* handle) 
{
  /* 如果回调函数timer_cb 是null,意味着定时器还未开始 */
  if (!handle->timer_cb) 
  {
    return UV_EINVAL;
  }
  //如果重复运行间隔不为0,那么以间隔值重新开始定时器
  if (handle->repeat) {
    uv_timer_stop(handle);
    uv_timer_start(handle, handle->timer_cb, handle->repeat, handle->repeat);
  }

  return 0;
}
5.设置重复间隔值,导出函数,在uv.h中声明,timer.c中定义
void uv_timer_set_repeat(uv_timer_t* handle, uint64_t repeat) 
{
  assert(handle->type == UV_TIMER);
  handle->repeat = repeat;
}
6.定时器handle关闭时的处理(uv_close)
void uv_close(uv_handle_t* handle, uv_close_cb cb) 
{
  uv_loop_t* loop = handle->loop;
  if (handle->flags & UV__HANDLE_CLOSING) {
    assert(0);
    return;
  }

  handle->close_cb = cb;

  /* Handle-specific close actions */
  switch (handle->type) 
  {
    ...
    case UV_TIMER:
      uv_timer_stop((uv_timer_t*)handle);//先停止定时器
      //uv__handle_closing(handle);展开:
      do {                                                                  
            assert(!((handle)->flags & UV__HANDLE_CLOSING));//不能处于正在关闭状态                                                          
            if (!(((handle)->flags & UV__HANDLE_ACTIVE) &&                      
                  ((handle)->flags & UV__HANDLE_REF))) 
               //非激活或者非引用状态下,先将loop的活动handle数量加一
               //对于定时器,比如初始化但未开始的情况下,或者调用了uv_timer_stop之后。只有
               //UV__HANDLE_REF,没有UV__HANDLE_ACTIVE                        
               uv__active_handle_add((uv_handle_t*) (handle));                                                      
            (handle)->flags |= UV__HANDLE_CLOSING;//正在关闭状态                              
            (handle)->flags &= ~UV__HANDLE_ACTIVE;//非激活状态                              
      } while (0)
      //内部函数
      uv_want_endgame(loop, handle);
      return;
    ...
  }
}

内部函数:

INLINE static void uv_want_endgame(uv_loop_t* loop, uv_handle_t* handle) 
{
  if (!(handle->flags & UV_HANDLE_ENDGAME_QUEUED)) 
  {
    //将本handle插入到loop需要关闭的handle列表的表头
    handle->flags |= UV_HANDLE_ENDGAME_QUEUED;
    handle->endgame_next = loop->endgame_handles;
    loop->endgame_handles = handle;
  }
}

最终,在uv_run中会调用内部函数处理所有需要关闭的句柄

INLINE static void uv_process_endgames(uv_loop_t* loop) {
  uv_handle_t* handle;

  while (loop->endgame_handles) {
    handle = loop->endgame_handles;
    loop->endgame_handles = handle->endgame_next;

    handle->flags &= ~UV_HANDLE_ENDGAME_QUEUED;

    switch (handle->type) 
    {
      ...
      case UV_TIMER:
        //处理定时器handle的关闭
        uv_timer_endgame(loop, (uv_timer_t*) handle);
        break;
      ...
    }
  }
}
void uv_timer_endgame(uv_loop_t* loop, uv_timer_t* handle)  
{
  if (handle->flags & UV__HANDLE_CLOSING) 
  {
    assert(!(handle->flags & UV_HANDLE_CLOSED));
    //uv__handle_close(handle);展开:
    do {
        //在handle队列中移除需要关闭的handle                                                                 
        QUEUE_REMOVE(&(handle)->handle_queue);
        //活动handle计数递减                              
        uv__active_handle_rm((uv_handle_t*) (handle));                      
        //状态更新为已关闭                                                                
        (handle)->flags |= UV_HANDLE_CLOSED;                                
        //调用回掉函数                                                                
        if ((handle)->close_cb)                                             
          (handle)->close_cb((uv_handle_t*) (handle));                     
    } while (0)
  }
}

整个定时器的关闭流程大致如下:
1.调用uv_close函数,针对uv_timer_t类型的handle,会先停止定时器

2.接着将loop中的活动handle计数加一,因为定时器handle如果调用过uv_timer_start,活动handle计数将会加一,而在调用了uv_timer_stop之后,会减一。此时无论是否调用过uv_timer_start,活动handle计数都没有再记录本handle,而接下来的操作仍然需要在loop中进行回调,所以需要此处在加一,之后的流程会减去。

3调用内部uv_want_endgame函数,讲本handle插入loop需要关闭的handle队列的头部。

4.uv_run中会在每个迭代中调用uv_process_endgames处理需要关闭的句柄。

5.在uv_process_endgames中,对于uv_timer_t会调用uv_timer_endgame

6.在uv_timer_endgame中,移除loop handle列表中的本handle,活动handle计数减一(对应第二步),跟新状态为UV__HANDLE_CLOSED,最后调用uv_close中传入的回调函数。

7.对于定时器的回调

在uv_run中会通过uv_process_timers处理所有的定时器

void uv_process_timers(uv_loop_t* loop) 
{
  uv_timer_t* timer;
  /* 调用定时器回调 */
  for (timer = RB_MIN(uv_timer_tree_s, &loop->timers);
       timer != NULL && timer->due <= loop->time;//因为是红黑树,找到第一个触发时间比当前时间 
                                                 //大的定时器之后就不用继续查找了
       timer = RB_MIN(uv_timer_tree_s, &loop->timers)) {

    uv_timer_stop(timer);
    uv_timer_again(timer);
    timer->timer_cb((uv_timer_t*) timer);
  }
}
8.uv_timer_tree_s红黑树使用的比较函数
static int uv_timer_compare(uv_timer_t* a, uv_timer_t* b) 
{
  //线比较触发时间
  if (a->due < b->due)
    return -1;
  if (a->due > b->due)
    return 1;
  //如果触发时间相同,比较start_id,start_id是在uv_timer_start函数中设置的当前loop的
  //timer_counter(只会递增,所以同一loop中不会出现相同的值),
  if (a->start_id < b->start_id)
    return -1;
  if (a->start_id > b->start_id)
    return 1;
  return 0;
}

libuv的定时器使用一颗全局的红黑树保存,以触发时间以及start_id作为key值比较,每次循环都会在更新了当前事件之后处理本loop所有的定时器,从出发时间最早的树节点(RB_MIN)开始。对于每一个定时器,处理流程如下:
1.先关闭,从红黑树删除
2.再以重复调用的间隔值重新开始,如果没有就意味着回调函数只调用一次,如果有重复间隔值,就以新的触发时间插入红黑树。
3.调用回调函数

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值