WdBly Blog

懂事、有趣、保持理智

周维的个人Blog

懂事、有趣、保持理智

站点概览

周维 | Jim

603927378@qq.com

推荐阅读

Node基于事件驱动服务异步I/O

Node是一个单线程、基于事件驱动的异步I/O后端Javascript运行平台,关于单线程、事件驱动、异步I/O我们应当如何理解呢?

事件驱动

  • keywords: 事件绑定、回调函数。
  • 实质:通过主循环加事件触发的方式来运行程序。
  • 用处:通过事件绑定和回调函数处理程序中的I/O(网络IO,文件IO数据库读写I/O或者客户端的事件IO)。

Node通过事件驱动的方式处理请求,无须为每一个请求创建额外的对应线程,可以省掉创建线程和销毁线程的开销,同时操作系统在调度任务时因为线程较少,上下文切换的代价很低。这使得服务器能够有条不紊地处理请求,即使在大
量连接的情况下,也不受线程上下文切换开销的影响,这是Node高性能的一个原因。

Nginx也是事件驱动的Web服务器。

事件驱动机制可能导致回调地狱问题。

Node通过事件驱动机制来运行程序, 所谓的事件驱动机制则是通过事件绑定和回调函数处理程序中的I/O的一种机制。

单线程

CPU

  • 单核CPU单位时间里,只能处理一个线程,那单核CPU如何处理多线程呢?答案是时间片和优先级。
  • 多核CPU处理机制和单核相同,只是单位时间处理得线程更多。
  • 单核CPU上运行的线程通过时间片切换执行,线程的切换会产生大量消耗,切换越多消耗越大。

传统多线程

  • 多线程执行的应用程序能有效提升多核CPU的利用率
  • 线程创建和切换有消耗。

Node单线程

  • 避免线程创建和切换的消耗。
  • 避免死锁问题。
  • 通过子进程 提高CPU利用率

异步I/O可以算作Node的特色,因为它是首个大规模将异步I/O应用在应用层上的平台,它力求在单线程上将资源分配得更高效。

异步I/O

操作系统里的I/O

  • 阻塞I/O: 程序需要等待I/O操作结果返回,而且CPU资源也会等待操作结果返回,大大浪费了CPU利用率。

  • 非阻塞I/O: 程序任然需等待I/O阻塞才可继续执行,在系统层面,通过轮询的方式询问是否有结果,但是在这期间CPU会切换到其它线程处理任务,提高CPU利用率。

Node中的异步I/O

  • linux中Node通过主线程 + 线程池 + 阻塞I/O模拟异步I/O。

image.png

  • windows中使用系统自带的ICOP实现异步I/O(实质是线程池)

image.png

Node异步I/O的实现

Node通过事件循环实现异步I/O,每一次事件循环也加一个Tick

Node是事件驱动模型,I/O操作通过事件绑定和回调函数机制执行。

这类不由开发者调用的回调函数的实现,就是Node的异步I/O。

当Node通过事件绑定注册异步回调时,经历了以下步骤:

第一阶段:注册I/O

  1. Node层生成请求对象request,请求对象中包含了回调函数和参数。
  2. 调用Node的核心模块,核心模块调用C++内建模块,内建模块通过libuv进行系统调用和包装请求对象。
  3. 将包装后的请求对象推入线程池等待执行,然后立即返回Node层继续向下执行。

因为包装对象被推入线程池时,不一定有空闲线程可以运行,所以可能会等待。

第二阶段:执行回调

  • 事件队列(观察者队列):每个Tick都需要一个或多个事件队列。 Node在每个Tick后,都会询问事件队列是否有新事件,如果有则取出执行。
  1. 线程池中的I/O操作得到结果后,归还线程至线程池。在每一个Tick,事件队列都会检查线程池是否有执行完的请求,然后将请求加入自身队列中。
  2. Node主线程询问事件队列是否有新事件,并取出执行。

image.png

非I/O的异步API

Node中存在一些与I/O无关的异步操作,它们分别是setTimeout()、setInterval()、setImmediate()、promise和process.nextTick()。

  • setTimeout()和setInterval(): 由它们创建的回调函数会被加入到定时器事件队列的数据结构中。在一个Tick结束后,会检查数据结构中的回调事件是否达到时间,然后立即执行。
  • setImmediate(): 延迟函数执行,属于check观察者。
  • process.nextTick(): 每次调用process.nextTick()方法,只会将回调函数放入队列中,在下一轮Tick时取出执行。

事件循环对观察者的检查是有先后顺序的,process.nextTick()属于idle观察者,setImmediate()属于check观察者。在每一个轮循环检查中,idle观察者先于I/O观察者,I/O观察者先于check观察者。

Node的异步I/O和API在每个Tick的执行情况和顺序如下。

image.png

在node主程序中执行setImmediate与setTimeout(fn,0)的顺序是随机的无法确定的

总结

  • 所谓的事件驱动机制则是通过事件绑定和回调函数处理程序中的I/O的一种机制。
  • 利用单线程,远离多线程死锁、状态同步等问题;利用异步I/O,让单线程远离阻塞,以更好地使用CPU。

参考资源:
《深入浅出Node.js》

提交

全部评论0

暂时没有评论...