带你了解各种 I/O 模型

1,484 阅读6分钟

前言

说起 I/O ,相信前端开发者最熟悉的就是网络请求了。 因为其他大部分 I/O 操作浏览器都帮我们完成了。但作为一名软件开发者,I/O 是我们无法避免要关注的东西。今天我们就来看看目前常见的各种 I/O 模型。

什么是I/O?

I/O 来源于英文的输入/输出(input/output) , 指数据在存储器(內部和外部)或其他周边设备之间的输入和輸出。通常用户进程中的一个完整IO分为两阶段:用户进程空间与内核空间的I/O、内核空间与设备空间I/O(磁盘、网络等)。分为 内存I/O网络I/O磁盘I/O 三种,通常我们说的I/O指的是后两者。

阻塞 I/O(Blocking I/O)

阻塞 I/O 是最常用的模型,对开发者最友好。因为在这种模型下,运行逻辑是线性的,开发者只要关注程序从头到尾的运行逻辑即可。

这个模型下应用线程与内核之间的交互行为模式是这样的:

从上图可以看出在阻塞 I/O 模型 下,我们从调用系统读取数据,到应用获得数据这个过程是线性的,这很符合我们的正常思考顺序。但这个模式却有一个极大的缺点,就是在操作系统准备数据的过程中,应用线程是阻塞的,一直处于空闲。

因此,在这个模型下,一个线程仅能处理一个网络连接上的数据通信。 即便连接上没有数据,线程也只能阻塞在对 Socket 的读操作上(以等待对端的数据)。 虽然这个模型对应 用整体来说是低效的,但对开发人员来说,这个模型却是最容易实现和使用的,所以,各 大平台在默认情况下都将 Socket 设置为阻塞的。

非阻塞 I/O(Non-Blocking I/O)

非阻塞 I/O 模型有别于阻塞 I/O ,它的应用线程与内核之间的交互行为模式是这样的:

可以看到,与阻塞 I/O 模型正相反,在非阻塞 I/O 模型下,当用户空间线程向操作系统内核发起 I/O 请求 后,内核会执行这个 I/O 操作,如果这个时候数据尚未就绪,就会立即将“未就绪” 的状 态以错误码形式(比如:EAGAIN/EWOULDBLOCK) , 返回给这次 I/O 系统调用的发起 者。 而后者就会根据系统调用的返回状态来决定下一步该怎么做。

在非阻塞模型下,位于用户空间的 I/O 请求发起者通常会通过轮询的方式,去一次次发起 I /O 请求,直到读到所需的数据为止。 不过,这样的轮询是对 CPU 计算资源的极大浪费, 因此,非阻塞 I/O 模型单独应用于实际生产的比例并不高。

I/O 多路复用(I/O Multiplexing)

为了避免非阻塞 I/O 模型轮询对计算资源的浪费,同时也考虑到阻塞 I/O 模型的低效,开发人员首选的网络 I/O 模型,逐渐变成了建立在内核提供的多路复用函数 select/poll 等 ( 以及性能更好的 epoll 等函数)基础上的 I/O 多路复用模型

这个模型下,应用线程与内核之间的交互行为模式如下图:

从图中我们看到,在这种模型下,应用线程首先将需要进行的 I/O 操作,都添加到多路复用函数中(这里以 select 为例) , 然后阻塞,等待 select 系统调用返回。 当内核发现有数据到达时,对应的 Socket 具备了通信条件,这时 select 函数返回。 然后用户线程 会针对这个 Socket 再次发起网络 I/O 请求,比如一个 read 操作。 由于数据已就绪,这次 网络 I/O 操作将得到预期的操作结果。

相比于阻塞模型一个线程只能处理一个 Socket 的低效,I/O 多路复用模型中,一个应用线程可以同时处理多个 Socket。 同时,I/O 多路复用模型由内核实现可读 / 可写事件的通知,避免了非阻塞模型中轮询,带来的 CPU 计算资源浪费的问题。

目前,主流网络服务器采用的都是“I/O 多路复用” 模型,有的也结合了多线程。 不过, I/O 多路复用 模型在支持更多连接、 提升 I/O 操作效率的同时,也给使用者带来了不小的复杂度,以至于后面出现了许多高性能的 I/O 多路复用框架,比如: l i bevent、 l i bev、 l i buv等, 以帮助开发者简化开发复杂性,降低心智负担。

非阻塞异步I/O

业内一直有一个疑问:NodeJs 能做后台吗?

其实这个问题早已有答案,阿里的 EggJs 就是 NodeJs 的产物。淘宝双十一的海量并发处理的背后就是 EggJs 的服务。可以说 NodeJs 早已证明了自己能在后端市场占据一席之地。NodeJs 的并发处理能力之所以如此的强,是因为它的 非阻塞异步I/O 机制。

非阻塞异步I/O 中定义了同步与异步两种行为,其中需要I/O操作的就是异步行为,其他的正常操作都是同步。在这种模型下,应用线程与内核之间的交互行为模式如下图:

我们可以看到非阻塞异步I/O 模型最大的特点就是不阻塞。同应用线程遇到异步操作之后,会继续执行同步操作,此时应用与系统两个线程就可以各自工作。等系统数据响应且应用线程同步操作执行完之后,应用就开始继续执行之前的异步操作。

这种模式可以极大地提高了CPU的利用率,并且大幅提高应用的并发处理能力。

总结

今天我们简单地把目前业内几种常见的I/O模型了解了一遍。软件开发不仅仅是业务层的开发,或者API的调用。了解这些 I/O模型的思想设计,无论对我们的底层开发能力,还是代码设计能力都有极大的帮助。

如果你觉得本文对你有一点帮助,麻烦给我点个赞吧~~ 谢谢