node.js异步方法的底层原理初探

作者:杨润炜
日期:2022/1/2 10:42

作为node.js开发者,使用了它好几年。对于它能够通过单线程完成并发操作处理,一开始只觉得这工具用起来简单又高效,后来在操作系统和其它编程语言的学习中,看到了不少实现并发的方案,如多进程、多线程、协程、同步原语、锁、信号量,这些方案都伴随着一定的开发复杂度和且有系统稳定性风险,再回过头看node.js能提供如此简便的方案,不由心生好奇,想弄清楚它是怎样实现的。
之前也看过一些node.js原理的资料,但只是记得它们跟异步原理应该有关系。我想把它们和想要探索的问题关联起来,这样也能把之前学习的东西串起来,应该会有更大的收获。

以下我通过提问题再找答案的方式来解释这一原理。(提问题找解答的方式能使学习的目标感更强,让学习更有效率。)

问题域

以下是具体的几个问题:

  1. node.js异步方法是怎样实现的?
  2. 事件循环是怎样运行?
  3. 调用栈、任务队列是什么,它们是怎样配合?
  4. 所有的异步操作都交由线程池负责么?

解答区

下面先通过图表的方式,展示我所理解的node.js异步方法的底层原理全貌。

图1. node.js异步方法底层架构

(以linux为例)
node.js异步方法架构2

图2. node.js代码执行流程

(以linux为例)
node.js代码执行流程

1. node.js异步方法是怎样实现的?

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

2. 事件循环是怎样运行?

参照图1,我们可以看到整个Eventl loop的架构。
事件循环被分为几个阶段,每个阶段都具有自己的队列。

  • Timers: 负责setTimeout的回调
  • Pending callbacks:一些系统操作的回调,如tcp error;
  • Idel, Prepare:只在内部使用;
  • Poll:负责各种IO事件的监听和数据接收;
  • Check:负责setImmediate的回调;
  • Close callbacks:负责close事件的回调。
    以上属于node.js的宏任务,至于process.nextTickPromise则属于微任务,会在每个宏任务切换间被检查和执行,所以它们往往会比较快被放入主线程栈执行。

3. 调用栈、任务队列是什么,它们是怎样配合?

如图2所示。

  • 调用栈是指node.js主线程的代码栈,它负责执行开发者给出的代码逻辑;
  • 任务队列是存储回调操作的,在事件循环中执行;

两者怎样配合:
调用栈若执行了异步调用,这时准备推入任务队列,主线程会将可以通过线程池执行的封装为异步对象后传入线程池处理,其它的放入任务队列,通过事件循环机制检查和执行。

4. 所有的异步操作都交由线程池负责么?

不是的。
线程池只负责一些特定的异步调用:

  • 文件IO;
  • 阻塞型的网络IO:dns操作;
  • cpu计算操作,如:加密(Crypto)、压缩(zlib);
  • C++ add-on可以指定使用线程池;

其它的异步操作则在主线程上执行,如:

  • 一些非阻塞的网络IO直接传给操作系统执行;

总结

node.js利用事件的机制实现了异步方法,解决了并发问题。

参考

The Node.js Event Loop
The Node.js Event Loop, Timers, and process.nextTick()
Don’t Block the Event Loop (or the Worker Pool)
Node’s Event Loop From the Inside Out by Sam Roberts, IBM
《node.js调试指南》

感谢您的阅读!
如果看完后有任何疑问,欢迎拍砖。
欢迎转载,转载请注明出处:http://www.yangrunwei.com/a/121.html
邮箱:glowrypauky@gmail.com
QQ: 892413924