之前断断续续开发过一些 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 阶段完成多个任务,完成后会立即回调【阻塞式的完成】。

(未完待续)

p.p1 { margin: 0; font: 12px "Helvetica Neue" }
li.li1 { margin: 0; font: 12px ".PingFang SC" }
span.s1 { font: 12px "Helvetica Neue" }
ol.ol1 { list-style-type: decimal }

最新文章

  1. python :模态对话框
  2. Linux中的硬链接和软链接
  3. js-JavaScript高级程序设计学习笔记5
  4. IIS------配置.Net 4.0
  5. UITabBarController的使用和坑
  6. Javascript模块化编程(一):模块的写法 作者: 阮一峰
  7. Centos 安装 neo4j
  8. AngularJS_对象数组-filter-orderBy
  9. android改变字体的颜色的三种方法
  10. 详谈easyui datagrid增删改查操作
  11. ubuntu 解压rar
  12. memmove 的实现
  13. HDU 3265 Posters(线段树)
  14. iOS开发常识
  15. Beta冲刺NO.5
  16. onkeypress 在js函数返回false后没有反应
  17. take a cpu core offline
  18. 17.Setters/getters
  19. Verilog中的阻塞与非阻塞
  20. 22.python中的面向对象和类的基本语法

热门文章

  1. Fiegn 声明式接口调用
  2. day15-SpringMVC执行流程
  3. 自动化测试方案对比:Katalon vs Python
  4. VS Ctrl+D 快速复制上一行
  5. Android Studio 模拟器(AVD)访问本机服务器
  6. Python爬虫:原来微博上的视频下载链接在这啊
  7. LeetCode-838 推多米诺
  8. yak远程服务端搭
  9. Dynamics365 DOC
  10. [NPUCTF2020]认清形势,建立信心