前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >理解libuv的基本原理

理解libuv的基本原理

作者头像
theanarkh
发布2020-05-11 11:29:47
1.6K0
发布2020-05-11 11:29:47
举报
文章被收录于专栏:原创分享原创分享

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,这时候事件循环就结束了。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-05-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 编程杂技 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档