总结 Node.js 底层异步原理
in Note with 0 comment
总结 Node.js 底层异步原理
in Note with 0 comment

日常工作中,大量使用到 Node.js, 之前也看过一些资料,这次打算总结一下,加深印象,写出来后也方便后续查阅。

事件循环

事件循环是 Node.js 最核心的概念,所以理解事件循环如何运作对于写出正确的代码和调试是非常重要的。

在计算机领域中事件循环(event loop),又称为消息分发器(message dispatcher)、消息循环(message loop)、消息泵(message pump)或运行循环(run loop),是一种程序构造或设计模式,负责等待并分发程序中的事件或消息。它的工作方式是向内部或者外部的“事件提供方”发出请求(请求通常会被阻塞,直到有新事件产生),待请求被处理后调用所获得的事件对应的回调函数(即“分发事件”)。

Event Loop 可以简单理解为:

Node.js

我们常说 Node.js 是单线程的,但为何能达到高并发呢?原因就在于底层的 libuv 维护一个 I/O 线程池(即上述的 “任务队列”),结合 Node.js 异步 I/O 的特性,单线程也能达到高并发啦。如下所示:

2019-04-17T15:04:27.png

事件循环的操作的顺序如下所示:

   ┌───────────────────────────┐
┌─>│           timers          │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │     pending callbacks     │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │       idle, prepare       │
│  └─────────────┬─────────────┘      ┌───────────────┐
│  ┌─────────────┴─────────────┐      │   incoming:   │
│  │           poll            │<─────┤  connections, │
│  └─────────────┬─────────────┘      │   data, etc.  │
│  ┌─────────────┴─────────────┐      └───────────────┘
│  │           check           │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
└──┤      close callbacks      │
   └───────────────────────────┘

每个框将被称为事件循环的阶段(phase)
每个阶段都有一个FIFO回调队列要执行。而每一个阶段一般来说,当事件循环进入给定的阶段,它将执行特定于该阶段的任何操作,然后在该阶段的队列中执行回调,直到队列已被已耗尽或已执行最大数量的回调。当队列已耗尽或达到回调限制,则事件循环将进入下一阶段,依此类推。

每个阶段的作用:

以上属于 Node.js 的宏任务,至于 process.nextTick 和 Promise 则属于微任务,会在每个宏任务切换间被检查和执行,所以它们往往会比较快被放入主线程栈执行。

Node.js 将异步操作放入线程池执行后,在事件循环中监听,线程池处理完任务后通过Epoll通知事件循环执行事件完成的回调操作。

libuv

libuv 是一个强制使用异步、事件驱动编程风格的库。其核心工作是提供事件循环和基于回调的 I/O 和其他活动通知。libuv 提供了许多核心实用工具,如定时器、非阻塞网络支持、异步文件系统访问和子进程等,以支持开发者进行高效的异步编程。

2019-04-17T15:05:01.png

具有下面这些特征:

线程池

Node.js 使用少量的线程来处理许多客户端的需求。 在 Node.js 中,有两种类型的线程:一个事件循环(即主循环、主线程、事件线程等),以及 Worker Pool(也称为线程池)中的 k 个 Worker 的池。

Node.js 使用 Worker Pool 来处理“昂贵”的任务。 这包括操作系统不提供非阻塞版本的I/O任务,和CPU任务。

I/O密集型:

CPU 密集型:

参考

Responses