浅谈NodeJs异步I/O-2
发布于 5 年前 作者 halu886 2522 次浏览 来自 分享

该文章阅读需要7分钟,更多文章请点击本人博客halu886

异步I/O与非阻塞I/O

异步I/O在Node中最为广泛,但是这并非Node原创

Node介绍时,异步,非阻塞和回调通常一起推介出来。异步和非阻塞听起来似乎是一回事,且从实际效果来看,都实现了并行I/O的效果。但是从计算机内核来说,异步/同步和阻塞/非阻塞其实是两回事。

操作系统内核对于I/O只有阻塞/非阻塞。当调用阻塞I/O时,那么应用程序只有等待I/O完成返回结果。

阻塞I/O的一个特点就是,需要系统内核完成所有操作才算调用结束。例如读取磁盘上的一个文件,需要系统内核完成磁盘寻道,读取数据,复制数据到内存中,才算整个调用结束。

阻塞I/O会使CPU进行等待,CPU的性能得不到充分利用,为了提升CPU的使用性能,内核提供了非阻塞I/O,区别在于调用后立马返回结果。

阻塞I/O的调用过程 1

非阻塞I/O的调用过程 2

操作系统把任意支持输入输出设备都抽象成文件,应用程序要对文件操作时,需要获取文件操作符作为与系统内核的凭证。如果应用程序需要调用I/O,需要先打开文件描述符,再通过文件描述符进行文件读写。阻塞I/O的区别是完成整个获取数据的过程再返回,然而非阻塞I/O则不带数据直接返回,之后在通过文件操作符进行读取。

非阻塞I/O直接返回,CPU时间片可以用来处理其他事物,此时的性能是明显的。

非阻塞I/O也存在一个问题,当非阻塞I/O返回后,结果不是业务层需要的数据,这个时候就需要不停的确认I/O是否完成,这个就叫做轮询技术。

任意技术并非完美,阻塞I/O造成了CPU等待浪费,而轮询则会导致CPU资源的浪费,那么我们来看一下如何优化轮询减小轮询所造成的CPU的浪费。

read

这是性能最低的一种方式,通过重复调用检查I/O的状态来完成完整数据的读取,在获取完整数据时,CPU一直耗用再等待上。 3

select

基于read的进行一种优化,通过文件描述符进行的事件状态进行判断。不过这有一个限制,它是由一个1024位数组来存储状态的,所以最多存储1024个文件描述符 4

poll

基于select进行优化的方案,采用链表的思路进行存储文件描述符,并且避免了没有必要的检查。但是当文件描述符比较多时,性能还是比较低效。 5

epoll

该方案是Linux下性能最高的方案,当进入到轮询时,没有检查到I/O事件时,将会进行休眠,直至事件发送将它唤醒。它真实的利用事件通知,执行回调的方法,而不是遍历查询。所以不会浪费CPU,不会浪费CPU,执行效率较高。 6

kqueue

该方案与epoll类似,不过只在FreeBSD系统下存在

轮询机制能够确保非阻塞I/O获取完整数据,但是对于应用程序而言,仍是同步I/O,需要I/O完成读取读取所有数据才算完成,在这个过程中,CPU要么在轮询的过程中或者休眠的过程中。结论还是不够好。

理想的非阻塞异步I/O

尽管epoll已经降低了CPU的耗用,但是休眠期间CPU几乎是闲置的,对于线程来说,CPU的利用率并不高。

我们期待的异步I/O是由应用程序进行发起非阻塞调用,然后直接处理下一件任务,然后I/O完成后通过信号或回调返回给应用程序。

7 Linux存在一种原生的异步I/O方式(AIO)就是通过信号和回调来传递数据,但是仅支持内核I/O中的O_DIRECT方式读取,导致无法利用系统缓存。

显式的异步I/O

实现异步I/O也并非难事,如果是多线程的话又是另外一番风景。通过部分线程使用阻塞I/O或者非阻塞I/O加轮询技术进行读取数据,再用一个进程作为计算进程,最后再通过进程间通信实现异步I/O。

8 glibc采用的就是通过线程池模拟异步I/O,但是存在一些难以忍受的bug,不推荐使用。libev的作者实现了一个异步I/O:libeio。libeio实际上还是线程池与阻塞I/O模拟异步I/O。最初Node在*nix平台下libeio配合libev实现异步I/O。在Nodev0.9.3中,自行实现了线程池来完成异步I/O。

另一种方案则是windows下的IOCP,在某种程度是一种理想的异步I/O方案,调用异步方法,等待I/O完成后的通知,执行回调,无需考虑轮询。但是本质上也是线程池加阻塞I/O的,不过线程池由系统内核接管。

IOCP和NodeJs的异步I/O调用模型相似,所以在Windows中采用IOCP实现异步I/O。

由于Windows平台与*nix平台下的差异,Node提供了libuv作为抽象封装层保证上层Node.js与自定义线程池或IOCP各自独立。在编译时,选择性的将win目录下或者unix下的源文件编译进目标程序中 9

强调一点,在*nix,任何计算机资源都抽象为文件。磁盘文件,硬件,套接字等等。
另外一点,我们所说的Node为单线程,指的是Javascript执行在单线程中,但是异步I/O另有线程池。

以上知识点均来自<<深入浅出Node.js>>,更多细节建议阅读书籍:-)

回到顶部