首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Libuv简介

Libuv简介

作者头像
theanarkh
发布2020-05-20 16:57:21
1.2K0
发布2020-05-20 16:57:21
举报
文章被收录于专栏:原创分享原创分享

Libuv是一个跨平台的的基于事件驱动的异步io库。但是他提供的功能不仅仅是io,包括进程、线程、信号、定时器、进程间通信等。下面是来自官网对Libuv架构的介绍图。

从上图中我们看到

  • Libuv使用各平台提供的事件驱动模块实现异步(epoll, kqueue, IOCP, event ports)。他用来支持上层非文件io的模块。libuv把上层的事件和回调封装成io观察者(uv__io_t)放到底层的事件驱动模块。当事件触发的时候,libuv会执行io观察者中的回调。
  • Libuv实现一个线程池用来支持上层文件io、dns以及用户层耗cpu的任务。

Libuv的整体执行架构

从上图中我们大致了解到,Libuv分为几个阶段,然后在一个循环里不断执行每个阶段里的任务。下面我们具体看一下每个阶段。

1 更新当前事件,在每次事件循环开始的时候,libuv会更新当前事件到变量中,这一轮循环的剩下操作可能使用这个变量获取当前事件,避免过多的系统调用影响性能。

2 如果时间循环是处于alive状态,则开始处理事件循环的每个阶段。否则退出这个事件循环。alive状态是什么意思呢?如果有active和ref状态的handle,active状态的request或者closing状态的handle则认为事件循环是alive的(具体实现后续会分析)。

3 timer阶段:判断最小堆中的节点哪个节点超时了,执行他的回调。

4 pending阶段:执行pending回调。一般来说,所有的io回调(网络,文件,dns)都会在poll io阶段执行。但是有的情况下,poll io阶段的回调会延迟到下一次循环执行,那么这种回调就是在pending阶段执行的。

5 idle阶段:如果节点处理avtive状态,每次事件循环都会被执行(idle不是说事件循环空闲的时候才执行)。

6 prepare阶段:和idle阶段一样。

7 poll io阶段:计算最长等待时间timeout,计算规则: 如果时间循环是以UV_RUN_NOWAIT模式运行的,则timeout是0。 如果时间循环即将退出(调用了uv_stop),则timeout是0。 如果没有active状态的handle或者request,timeout是0。 如果有dile阶段的队列里有节点,则timeout是0。 如果有handle等待被关闭的(即调了uv_close),timeout是0。 如果上面的都不满足,则取timer阶段中最快超时的节点作为timeout,如果没有则timeout等于-1,即永远阻塞,直到满足条件。

8 poll io阶段:调用各平台提供的io多路复用接口,最多等待timeout时间。返回的时候,执行对应的回调。(比如linux下就是epoll模式)

9 check阶段:和idle prepare一样。

10 closing阶段:处理调用了uv_close函数的handle的回调。

11 如果libuv是以UV_RUN_ONCE模式运行的,那事件循环即将退出。但是有一种情况是,poll io阶段的timeout的值是timer阶段的节点的值。并且poll io阶段是因为超时返回的,即没有任何事件发生,也没有执行任何io回调。这时候需要在执行一次timer阶段。因为有节点超时了。

12 一轮事件循环结束,如果libuv以UV_RUN_NOWAIT 或 UV_RUN_ONCE模式运行的,则退出事件循环。如果是以UV_RUN_DEFAULT模式运行的并且状态是alive,则开始下一轮循环。否则退出事件循环。

下面是Libuv事件循环实现的逻辑。

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;
  }
  // 是因为调用了uv_stop退出的,重置flag
  if (loop->stop_flag != 0)
    loop->stop_flag = 0;
  // 返回是否还有活跃的任务(handle或request),业务代表可以再次执行uv_run
  return r;
} 

文档 http://docs.libuv.org/en/v1.x/design.html

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

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

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

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

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