Node的单线程异步非阻塞I/O模型
Node.js® is a JavaScript runtime built on Chrome’s V8 JavaScript engine. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient.
说异步之前先了解几个名词
I/O的阻塞与非阻塞:阻塞模式的I/O会造成应用程序等待,直到I/O完成。同时操作系统也支持将I/O操作设置为非阻塞模式,这时应用程序的调用将可能在没有拿到真正数据时就立即返回了,为此应用程序需要多次调用才能确认I/O操作完全完成,这就是一个轮训的过程。
I/O的同步与异步:I/O的同步与异步出现在应用程序中。如果做阻塞I/O调用,应用程序等待调用的完成的过程就是一种同步状况。相反,I/O为非阻塞模式时,应用程序则是异步的。
事件驱动:当事件被检测到发生时才回调调用会到函数,通过事件循环加事件触发的方式来运行程序。
事件循环:当有大量异步操作时需要调用相应的回调函数,需要用一种机制来管理同一个队列。
Node的单线程异步非阻塞I/O模型利用单线程,远离多线程的死锁、状态同步等问题避免不必要的内存开销和上下文切换开销。 利用异步I/O,让单线程远离阻塞,更好的利CPU。
一个异步 I/O 的大致流程如下:
发起 I/O 调用
- 用户通过 Javascript 代码调用 Node 核心模块,将参数和回调函数传入到核心模块;
- Node 核心模块会将传入的参数和回调函数封装成一个请求对象;
- 将这个请求对象推入到 I/O 线程池等待执行;
- Javascript 发起的异步调用结束,Javascript 线程继续执行后续操作。
执行回调
- I/O 操作完成后,会将结果储存到请求对象的 result 属性上,并发出操作完成的通知;
- 每次事件循环时会检查是否有完成的 I/O 操作,如果有就将请求对象加入到 I/O 观察者队列中,之后当做事件处理;
- 处理 I/O 观察者事件时,会取出之前封装在请求对象中的回调函数,执行这个回调函数,并将 result 当参数,以完成 Javascript 回调的目的。
这里面涉及到了一个设计理念:事件循环(Event Loop),它是一个类似于 while true 的无限循环,它会维护一系列的监视器,这些监视器都有对应着一种异步操作,它们注册事件监听以及相应的回调。事件循环除了维护那些观察者队列,还维护了一个 time 字段,在初始化时会被赋值为0,每次循环都会更新这个值。所有与时间相关的操作,都会和这个值进行比较,来决定是否执行。与 timer 相关的过程如下:
- 更新当前循环的 time 字段,即当前循环下的“现在”;
- 检查循环中是否还有需要处理的任务(handlers/requests),如果没有就不必循环了,即是否 alive。
- 检查注册过的 timer,如果某一个 timer 中指定的时间落后于当前时间了,说明该 timer 已到期,于是执行其对应的回调函数;
- 执行一次 I/O polling(即阻塞住线程,等待 I/O 事件发生),如果在下一个 timer 到期时还没有任何 I/O 完成,则停止等待,执行下一个 timer 的回调。如果发生了 I/O 事件,则执行对应的回调;由于执行回调的时间里可能又有 timer 到期了,这里要再次检查 timer 并执行回调。
Node.js它的单线程指的是自身 Javascript 运行环境的单线程,Node.js 并没有给 Javascript 执行时创建新线程的能力,最终的实际操作是通过事件循环来执行的。