【8.30】node 原理 - 异步 I/O - 异步 I/O 在操作系统层面支持状况

626 阅读3分钟

这是我参与8月更文挑战的第28天,活动详情查看:8月更文挑战

上一篇文章从用户体验、资源分配,这两个方面介绍了我们为什么需要异步 I/O。这一篇文章主要讲异步 I/O 在操作系统层面的支持状况。

异步 I/O 在 nodejs 中应用的最广泛,但是他不是 nodejs 的原创,我们来看一下操作系统对异步 I/O 的支持状况。

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

异步和非阻塞这两个概念听起来没什么区别,都可以达到我们并行 I/O 的效果,但是实际上对于操作系统而言,他们不是一回事。

操作系统内核对于 I/O 而言只有两种处理方式:阻塞和非阻塞。

在调用阻塞 I/O 时,应用程序需要等待 I/O 完成后,也就是操作系统内核完成所有工作后,才返回结果。这样会造成 CPU 等待 I/O,CPU 无法得到充分的利用,如下图:

image.png

于是为了提高性能,内核提供了非阻塞 I/O 的方式,非阻塞 I/O 调用结束后会立即返回,如下图:

image.png

非阻塞 I/O 返回之后,CPU 时间片可以去处理其他的事情,这样对比于阻塞 I/O,可以提升性能。

但是因为非阻塞 I/O 立即返回的并不是最终的结果,而是当前的一个调用状态,于是应用程序需要反复,也就是轮询调用 I/O,来获取这次操作是否完成。轮询的方式也需要 CPU 去处理操作的结果,对于应用程序来说也是要不断的发送 I/O 请求,是有它的缺点的。

理想的非阻塞异步 I/O

我们期望的异步 I/O 是应用程序发起一个非阻塞的调用,无需通过遍历或者轮询的方式获取结果,可以直接处理后面的任务,在 I/O 完成后直接通过信号或者回调返回给应用程序即可,如下图:

image.png

Linux 上存在上面描述的这种方式,叫做 AIO,但是它只在 Linux 上存在,并且不能利用系统缓存。

现实的异步 I/O

我们前面讨论的是单线程的情况,如果使用多线程,是可以模拟理想情况的。可以让部分线程进行阻塞或非阻塞+轮询来进行 I/O 操作,让一个线程进行计算处理,通过线程间的通信将 I/O 的数据进行传递,如下图:

image.png

*nix 和 windows 系统上分别用不同方式实现了异步 I/O,分别是自定义线程池和 IOCP,nodejs 的 libuv 层抹平了不同操作系统之间的差异,保证 nodejs 的上层实现和操作系统层面的实现互相独立,如下图:

image.png

之前我们提到 nodejs 是单线程的,其实只是在 JavaScript 执行层面是单线程的,实际不论是在 *nix 和 windows 上,内部完成 I/O 任务另有线程池的。

本文是对深入浅出nodejs中异步I/O一章的学习总结,欢迎评论和点赞~