zoukankan      html  css  js  c++  java
  • Node.js源码解读-EventLoop

     之前断断续续开发过一些Node.js的项目,但只仅限于使用它实现一些功能,没有过多对底层深入的研究。现在因为公司大前端组内的服务端渲染直出、BFF(Backend For Frontend)等需求会越来越多,组内需要对服务端技术有更深刻的理解,如果对Node.js仅仅停留在如何写业务代码的层面,那恐怕是没有底气保证以后服务的稳定性。

      本文会基于node-v12.13.0版本的源码,对核心模块代码做一些阅读和理解,以窥探Node.js服务高效的秘诀。在研究源码之前,首先带着几个疑问,看接下来是否能一一解开:

    1. EventLoop的任务调度方式是什么样的?一次Loop取一个任务还是多个任务?
    2. 主循环在没有任务处理的空闲时,如何休眠的?是像iOS的runloop调用系统进程挂起吗?

      事件循环的实现是属于libuv核心库的一部分,而在node项目中,他的位置是在deps/uv/src/目录下,打开core.c文件,其中的uv_run()就是主循环的入口函数,整个函数的实现也基本和官方文档给出的事件循环阶段一致。

    源码

     当要写这篇博客的时候,实际上已经有很多介绍这些内容的文章了,所以本篇就直接略过一些loop阶段,直接讲最重要的:

    1.计算超时时间

    超时时间直接决定了poll阶段是阻塞还是非阻塞的直接执行,所以他决定着任务调度的实际执行时机和执行方式,主要实现在uv_backend_timeout()函数里

    展开函数的实现:

    int uv_backend_timeout(const uv_loop_t* loop) {
      // loop将要停止时,返回0
    if (loop->stop_flag != 0) return 0;  // 没有活跃的handles时,返回0 if (!uv__has_active_handles(loop) && !uv__has_active_reqs(loop)) return 0;  // idle不为空时,返回0 if (!QUEUE_EMPTY(&loop->idle_handles)) return 0;  // pending_queue(执行任务已完成待回调队列)不为空时,返回0 if (!QUEUE_EMPTY(&loop->pending_queue)) return 0;  // closeing_handles不为空时,返回0 if (loop->closing_handles) return 0; return uv__next_timeout(loop); }

    接下来是uv_next_timeout()这个函数,它是实现在deps/uv/src/time.c文件里:

    int uv__next_timeout(const uv_loop_t* loop) {
      const struct heap_node* heap_node;
      const uv_timer_t* handle;
      uint64_t diff;
    
      heap_node = heap_min(timer_heap(loop));
     // timer队列为空时,返回-1(-1的含义在下面介绍)
    if (heap_node == NULL) return -1; /* block indefinitely */ handle = container_of(heap_node, uv_timer_t, heap_node);
     // 如果timer超时了,返回0
    if (handle->timeout <= loop->time) return 0;
     // 将超时时间设为最早要超时的timer的所剩余时间 diff
    = handle->timeout - loop->time; if (diff > INT_MAX) diff = INT_MAX; return (int) diff; }

    从上面的函数计算好的timeout将以参数的形式传入uv__io_poll()这个函数,这个函数内就是poll阶段的实现了,我已经迫不及待的要一探究竟了。uv__io_poll()依赖的操作系统提供的功能,具有平台相关性,所以不同的平台会有不同的实现,本文主要讨论linux-core.h这个文件,即linux平台的实现。

      代码比较长我就不贴了,总之看完并理解下来,uv_io_poll就是对epoll的一个封装,但uv给我们提供了一个很值得思考和借鉴的方法,那就是timeout的使用,根据这个timeout来动态决定一次loop将要处理的任务量。

    首选来解析一下epoll_pwait这个函数的作用

    .....
    ......    
    nfds = epoll_pwait(loop->backend_fd,
                           events,
                           ARRAY_SIZE(events),
                           timeout,
                           psigset);
    ......
    ......

    所以从任务队列取并不是简单的只取一条,在这个超时时间内,可能会在一次loop的poll阶段完成多个任务,完成后会立即回调【阻塞式的完成】。

     (未完待续)

  • 相关阅读:
    存储过程和触发器
    RuPengWang项目
    短信验证
    Lucene.Net 站内搜索
    Quartz 定时任务(含Redis)
    网上支付(支付宝/银联)
    iOS 图片选择的路径处理(转)
    iOS 使用cocoaPods总结 ----摩天居士博客
    iOS 开发之本地化 国际化
    iOS 8 Xcode6 设置Launch Image 启动图片<转>
  • 原文地址:https://www.cnblogs.com/liujixin/p/11966095.html
Copyright © 2011-2022 走看看