理解libuv的基本原理

2020-05-11 11:29:47 浏览数 (1)

libuv的实现是一个很经典生产者-消费者模型。libuv在整个生命周期中,每一次循环都执行每个阶段(phase)维护的任务队列。逐个执行节点里的回调,在回调中,不断生产新的任务,从而不断驱动libuv。今天我们分析一下libuv的整体架构,从而学会如何使用libuv。我们从libuv的一个小例子开始。

代码语言:javascript复制
#include <stdio.h>
#include <uv.h>

int64_t counter = 0;

void wait_for_a_while(uv_idle_t* handle) {
    counter  ;
    if (counter >= 10e6)
        uv_idle_stop(handle);
}

int main() {
    uv_idle_t idler;
     // 获取事件循环的核心结构体。并初始化一个idler
    uv_idle_init(uv_default_loop(), &idler);
    // 往事件循环的idle节点插入一个任务
    uv_idle_start(&idler, wait_for_a_while);
    // 启动事件循环
    uv_run(uv_default_loop(), UV_RUN_DEFAULT);
    // 销毁libuv的相关数据
    uv_loop_close(uv_default_loop());
    return 0;
}

使用libuv,我们首先需要获取libuv的核心结构体uv_loop_t。uv_loop_t是一个非常大的结构体。里面记录了libuv整个生命周期的数据。

uv_default_loop为我们提供了一个默认已经初始化了的uv_loop_t结构体。当然我们也可以自己去分配一个,自己初始化。

代码语言:javascript复制
uv_loop_t* uv_default_loop(void) {
  // 缓存
  if (default_loop_ptr != NULL)
    return default_loop_ptr;

  if (uv_loop_init(&default_loop_struct))
    return NULL;

  default_loop_ptr = &default_loop_struct;
  return default_loop_ptr;
}

libuv维护了一个全局的uv_loop_t结构体,使用uv_loop_init进行初始化。不打算展开uv_loop_init函数。因为他大概就是对uv_loop_t结构体各个字段进行初始化。接着我们看一下uv_idle_*系列的函数。 1 uv_idle_init

代码语言:javascript复制
 int uv_idle_init(uv_loop_t* loop, uv_idle_t* handle) {              

    /*
        初始化handle的类型,所属loop,打上UV_HANDLE_REF,
        并且把handle插入loop->handle_queue队列的队尾
    */
    uv__handle_init(loop, (uv_handle_t*)handle, UV_IDLE);                   
    handle->idle_cb = NULL;                                                 
    return 0;                                                                 
  } 

执行uv_idle_init函数后,libuv的内存视图如下

2 uv_idle_start

代码语言:javascript复制
 int uv_idle_start(uv_idle_t* handle, uv_idle_cb cb) {           

   // 如果已经执行过start函数则直接返回
    if (uv__is_active(handle)) return 0;                                      
    if (cb == NULL) return UV_EINVAL;                                         

    // 把handle插入loop中idle的队列
    QUEUE_INSERT_HEAD(&handle->loop->idle_handles, &handle->queue);         

    // 挂载回调,下一轮循环的时候被执行
    handle->idle_cb = cb;                                                   

   /*
       设置UV_HANDLE_ACTIVE标记位,并且loop中的handle数加一,
       init的时候只是把handle挂载到loop,start的时候handle才处于激活态
   */
    uv__handle_start(handle);                                                 
    return 0;                                                                 
  } 

执行完uv_idle_start的内存视图

然后执行uv_run进入libuv的事件循环。

代码语言:javascript复制
int uv_run(uv_loop_t* loop, uv_run_mode mode) {
  int timeout;
  int r;
  int ran_pending;
  // 在uv_run之前要先提交任务到loop
  r = uv__loop_alive(loop);
  // 事件循环没有任务执行,即将退出,设置一下当前循环的时间
  if (!r)
    uv__update_time(loop);
  // 没有任务需要处理或者调用了uv_stop 
  while (r != 0 && loop->stop_flag == 0) {
    // 更新loop的time字段
    uv__update_time(loop);
    // 执行超时回调
    uv__run_timers(loop);
    // 执行pending回调,ran_pending代表pending队列是否为空,即没有节点可以执行
    ran_pending = uv__run_pending(loop);
    // 继续执行各种队列
    uv__run_idle(loop);
    uv__run_prepare(loop);

    timeout = 0;
    // 执行模式是UV_RUN_ONCE时,如果没有pending节点,才会阻塞式poll io,默认模式也是
    if ((mode == UV_RUN_ONCE && !ran_pending) || mode == UV_RUN_DEFAULT)
      timeout = uv_backend_timeout(loop);
    // poll io timeout是epoll_wait的超时时间
    uv__io_poll(loop, timeout);
    uv__run_check(loop);
    uv__run_closing_handles(loop);
    // 还有一次执行超时回调的机会,因为poll io阶段可能是因为定时器超时返回的。
    if (mode == UV_RUN_ONCE) {
      uv__update_time(loop);
      uv__run_timers(loop);
    }

    r = uv__loop_alive(loop);
    // 只执行一次,退出循环,UV_RUN_NOWAIT表示在poll io阶段不会阻塞并且循环只执行一次
    if (mode == UV_RUN_ONCE || mode == UV_RUN_NOWAIT)
      break;
  }

  /* The if statement lets gcc compile it to a conditional store. Avoids
   * dirtying a cache line.
   */
  // 是因为调用了uv_stop退出的,重置flag
  if (loop->stop_flag != 0)
    loop->stop_flag = 0;
  // 返回是否还有活跃的任务(handle或request),业务代表可以再次执行uv_run
  return r;
}

我们看到有一个函数是uv__run_idle。这就是处理idle阶段的函数。我们看一下他的实现。

代码语言:javascript复制
 // 在每一轮循环中执行该函数,执行时机见uv_run
  void uv__run_idle(uv_loop_t* loop) {                                      
    uv_idle_t* h;                                                         
    QUEUE queue;                                                              
    QUEUE* q;                                                                 

    // 把该类型对应的队列中所有节点摘下来挂载到queue变量,变量回调里不断插入新节点,导致死循环
    QUEUE_MOVE(&loop->idle_handles, &queue);                                

   // 遍历队列,执行每个节点里面的函数
    while (!QUEUE_EMPTY(&queue)) {                                            

      // 取下当前待处理的节点
      q = QUEUE_HEAD(&queue);                                                 

      // 取得该节点对应的整个结构体的基地址
      h = QUEUE_DATA(q, uv_idle_t, queue);                                

      // 把该节点移出当前队列,否则循环不会结束
      QUEUE_REMOVE(q);                                                        

     // 重新插入原来的队列
      QUEUE_INSERT_TAIL(&loop->idle_handles, q);                            

     // 执行回调函数
      h->idle_cb(h);                                                        
    }                                                                         
  }   

我们看到uv__run_idle的逻辑并不复杂。就是遍历idle_handles队列的节点,然后执行回调。在回调里我们可以插入新的节点(产生新任务)。从而不断驱动libuv的运行。我们看到uv_run退出循环的条件下面的代码为false

代码语言:javascript复制
r != 0 && loop->stop_flag == 0

stop_flag由用户主动关闭libuv事件循环。

代码语言:javascript复制
void uv_stop(uv_loop_t* loop) {
  loop->stop_flag = 1;
}

r是代表事件循环是否还存活,这个判断的标准是由uv__loop_alive提供

代码语言:javascript复制
static int uv__loop_alive(const uv_loop_t* loop) {
  return loop->active_handles > 0 ||
         loop->active_reqs.count > 0 ||
         loop->closing_handles != NULL;
}

这时候我们有一个actived handles。所以libuv不会退出。当我们调用uv_idle_stop函数把idle节点移出handle队列的时候,libuv就会退出。

代码语言:javascript复制
int uv_idle_stop(uv_idle_t* handle) {                               
    if (!uv__is_active(handle)) return 0;                                     

    // 把idle节点从loop中idle队列移除,但是还挂载到handle_queue中
    QUEUE_REMOVE(&handle->queue);                                             

   // 清除active标记位并且减去loop中handle的active数
    uv__handle_stop(handle);                                                  
    return 0;                                                                 
  }     

执行uv_idle_stop后active_handles为0,这时候事件循环就结束了。

0 人点赞