3.2.1 异步I/O与非阻塞I/O
在听到Node介绍时,经常能听到异步,非阻塞,回调,事件等词,其中异步和非阻塞并不是一回事。
操作系统内核对于I/O只有两种方式:阻塞与非阻塞
阻塞I/O一个特点是调用之后一定要等到系统内核层面完成所有操作后,调用才结束。阻塞造成CPU等待,浪费等待时间,CPU的处理能力得不到充分利用。为了提高性能,内核提供了非阻塞I/O。非阻塞I/O调用完会立即返回。
但非阻塞也存在一些问题。由于完整的I/O并没有完成,立即返回的并不是业务层期望的数据,而仅仅是当前调用的状态。为了获取完成的数据,应用程序需要重复调用I/O操作来确认是否完成。这种重复调用判断是否完成的技术叫做轮训。
阻塞I/O带来的是CPU的浪费,非阻塞带来的麻烦却是需要轮询去确认是否完成数据获取,它会使CPU频繁的处理,从而造成非必要的浪费。
3.2.2 现实的异步I/O
在多线程情况下,可以通过让部分线程进行阻塞I/O或者非阻塞I/O加轮询技术来完成数据获取,让一个线程进行计算处理,通过线程之间的通信将I/O得到的数据进行传递,这就轻松实现了异步I/O
在*nix平台,libev作者重新实现了一个异步I/O库:libeio,它采用线程池与阻塞I/O模拟异步I/O。Window平台,异步I/O方案为IOCP,它通过调用异步方法,等待I/O完成之后的通知,执行回调,用户无须考虑轮询,但它内部其实仍是线程池原理。
由于Windows平台和*nix平台的差异,Node提供了libuv作为抽象封装层,使得所有平台兼容性的判断都由这一层来完成,并保证上层的Node与下层的自定义线程池与IOCP之间各自独立。这里封装的异步I/O不仅仅局限于磁盘文件读写,任何访问计算机资源都被封装为IO。
最后,就是Node中提到的单线程指的是Javascript线程。在Node底层,无论是*nix还是Windows平台,内部都存在多线程池。