NodeJS之异步I/O

  Node       2017-01-14

本文介绍Node中的异步I/O,将围绕着以下几个方面:

  • 什么是异步I/O?
  • 为什么要选择异步I/O?
  • Node中是如何实现的?

什么是异步I/O?

首先介绍几个概念,异步和I/O:

  • 异步:与同步相对,同步是等待上个任务执行完成后下个任务再执行;异步是则不必等待上个任务的执行结束就可以执行;
  • I/O:输入输出流,这里包括网络请求、文件处理、数据库操作等;

异步I/O是:在处理I/O操作时,发出相应的I/O请求后,不必等待请求结果,在得到相应的处理结果后,再调用后续的操作。

为什么要选择异步I/O?

简单点说,就是:选择异步I/O后,对于当前的任务,在请求I/O的过程中,可以并发处理其他的任务,更高效地利用计算资源。

多线程

Node的特点是:单线程、事件驱动、异步I/O。

选择单线程的原因是:多线程面临锁、状态同步、切换上下文开销等问题,单线程更符合编程习惯,异步I/O可以更好发挥Node在单线程上的价值。[多线程可以有效利用多核CPU,值得说明的是:Node也提供多线程处理方式]

Node中是如何实现的?

同步异步I/O和阻塞非阻塞I/O

同异步I/O是针对任务来说的,如果有多个任务,异步I/O中上一个任务不会阻塞下一步任务的执行;

阻塞和非阻塞I/O是应用程序读取系统内核中I/O的方式;单线程中,阻塞I/O是等待返回数据,这个时间片内的CPU处于等待状态;非阻塞I/O是调用之后立即返回, ~CPU的时间片可以处理其他事务?~ ,再采用轮询的方式来判断I/O操作是否完成。

现实中的异步I/O

通过多线程的方式来实现,部分线程进行阻塞I/O或非阻塞I/O+轮询完成数据获取,通过线程通信,实现异步I/O;

Node的异步I/O

  • 事件循环:Node自身的执行模型;启动进程时,会有一个类似while(true)的循环,每执行一次循环体,称为一个Tick;每个Tick就是从观察者那里查看是否还有事件待处理,如果没有,退出进程;
  • 观察者:向事件循环反馈是否有要处理的时间;有网络I/O观察者、文件I/O观察者等;
  • 请求对象:每一次异步I/O的中间产物,所有状态都在这个对象中,包括请求参数、执行结果、回调函数;
  • I/O线程池:操作系统层面的异步I/O实现方式;

整体流程如下图所示 [图片来源于《深入浅出NodeJS》]:
async-io

Node底层也是通过线程池的方式来实现的异步I/O;[上文说的Node是单线程的,指的是JavaScript代码执行在单线程中]

非I/O的异步API

定时器

setTimeout() 和 setInterval() :通过这两个方法创建的定时器会被插入到定时器观察者内部的红黑树中,每次Tick执行时,检查定时器对象是否超过定时时间,如果是,执行回调;

存在的问题:执行时间并非精确;

process.nextTick()

nextTick()指定回调在下一次事件循环中执行,比setTimeout(fn, 0)精确、轻量、高效;

setImmediate()

也是在下一次事件循环中执行,与nextTick()不同的是:

  • nextTick()执行顺序先于setImmediate(),因为对应的观察者不一样;
  • setImmediate()一次事件循环执行一个,而nextTick()不限个数;

参考链接

本文最后更新于2017-01-23 21:29:16