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阶段完成多个任务,完成后会立即回调【阻塞式的完成】。

     (未完待续)

  • 相关阅读:
    Nim or not Nim? hdu3032 SG值打表找规律
    Maximum 贪心
    The Super Powers
    LCM Cardinality 暴力
    Longge's problem poj2480 欧拉函数,gcd
    GCD hdu2588
    Perfect Pth Powers poj1730
    6656 Watching the Kangaroo
    yield 小用
    wpf DropDownButton 源码
  • 原文地址:https://www.cnblogs.com/liujixin/p/11966095.html
Copyright © 2011-2022 走看看