写点什么

io 模型

作者:wzh
  • 2022-10-14
    北京
  • 本文字数:1982 字

    阅读完需:约 7 分钟

4 种 io 模型

阻塞 io、非阻塞 io、io 多路复用、异步 io

阻塞与非阻塞:阻塞和非阻塞的概念描述的是用户线程调用内核 IO 操作的方式:阻塞是指 IO 操作需要彻底完成后才返回到用户空间;而非阻塞是指 IO 操作被调用后立即返回给用户一个状态值,不需要等到 IO 操作彻底完成。

同步与异步:可以将同步与异步看成发起 IO 请求的两种方式。同步 IO 是指用户空间(进程或者线程)是主动发起 IO 请求的一方,系统内核是被动接收方。异步 IO 则反过来,系统内核是主动发起 IO 请求的一方,用户空间是被动接收方。

1)同步 阻塞 io 模型:

  1. 用户进程发起系统调用 read

  2. 系统内核收到系统调用 read,就开始准备数据,比如,还没有收到一个完整的 tcp 包,系统内核就需要等待足够的数据到来。

  3. 内核一直等到完整的数据到达,就会将数据从内核缓冲区复制到用户缓冲区,然后内核返回结果(例如返回复制到用户缓冲区中的字节数)。

  4. 内核返回后,用户线程解除阻塞的状态,重新运行起来。

阻塞 io 的特点:在 IO 执行的两个阶段(等待数据和拷贝数据)用户进程 都被阻塞了。

优点是线程被挂起,不会浪费 cpu 资源;缺点是,一般情况下,一个线程维护一个连接的 IO 操作。但在高并发情况下,阻塞 IO 模型需要大量的线程来维护大量的网络连接,内存、线程切换开销会非常巨大。

2)同步 非阻塞 io 模型:


调用系统调用,当数据没有准备好,不会阻塞进程,而是返回一个错误。用户进程判断结果是一个错误,因此知道数据没有准备好,于是再次发送 read 操作。一旦内核中数据准备好了,且再次收到用户进程的系统调用时,就会开将数据复制到用户缓冲区,这个过程用户进程是阻塞的,直到完成数据从内核缓冲区复制到用户缓冲区。复制完成后,系统调用返回成功。用户进程(或者线程)读到数据后,才会解除阻塞状态,重新运行起来。

特点:为了读取最终的数据,用户进程需要不断地发起 IO 系统调用。优点:每次发起的 IO 系统调用在内核等待数据过程中立即返回,用户线程不会阻塞。实时性较好。缺点:需要不断轮询内核,这将占用大量的 CPU 时间。

从上图可以看到,在非阻塞的接口相比于阻塞型接口的显著差异在于被调用之后立即返回。

3)io 多路复用

io 多路复用模型可以避免非阻塞 io 中的轮询等待问题。支持 IO 多路复用的系统调用有 select、epoll 等。

  1. 选择器注册。首先,将需要 read 操作的目标文件描述符(socket 连接)提前注册到 Linux 的 select/epoll 选择器中(java 的 Selector 类)。然后,开启整个 IO 多路复用模型的轮询流程。

  2. 就绪状态的轮询。通过选择器的查询方法,查询所有提前注册过的目标文件描述符(socket 连接)的 IO 就绪状态。通过查询的系统调用,内核会返回一个就绪的 socket 列表。

  3. 用户线程获得了就绪状态的列表后,根据其中的 socket 连接发起 read 系统调用,用户线程阻塞。内核开始复制数据,将数据从内核缓冲区复制到用户缓冲区。

  4. 复制完成后,内核返回结果,用户线程才会解除阻塞的状态,用户线程读取到了数据,继续执行。

注意:在用户进程进行 IO 就绪事件的轮询时,需要调用选择器的 select 查询方法,发起查询的用户进程或者线程是阻塞的。(内核会“监视”所有 select 负责的 socket,当任何一个 socket 中的数据准备好了,select 就会返回)当然,如果使用了查询方法的非阻塞的重载版本,发起查询的用户进程或者线程也不会阻塞,重载版本会立即返回。

io 多路复用需要使用两个系统调用(select 和 recvfrom),而阻塞 io 只调用了一个系统调用(recvfrom)。用 select 的优势在于一个线程可以同时处理多个连接(选择器)。所以,如果处理的连接数不是很高的话,使用 select/epoll 的 Web server 不一定比使用多线程的阻塞 IO 的 Web server 性能更好,可能延迟还更大;select/epoll 的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接。

4)异步 io

本质上 select/epoll 系统调用是阻塞式的,属于同步 IO,需要在读写事件就绪后由系统调用本身负责读写,也就是说这个读写过程是阻塞的。而异步 io 可以彻底地解除线程的阻塞。

  1. 当用户线程发起了 read 系统调用后,立刻就可以去做其他的事,用户线程不阻塞。

  2. 内核开始 IO 的第一个阶段:准备数据。准备好数据,内核就会将数据从内核缓冲区复制到用户缓冲区。

  3. 内核会给用户线程发送一个信号(Signal),或者回调用户线程注册的回调方法,告诉用户线程 read 系统调用已经完成,数据已经读入用户缓冲区。

  4. 用户线程读取用户缓冲区的数据,完成后续的业务操作。

特点:在内核等待数据和复制数据的两个阶段,用户线程都不是阻塞的。

理论上,异步 IO 是真正的异步输入输出,它的吞吐量高于 IO 多路复用模型的吞吐量。就目前而言,Windows 系统下通过 IOCP 实现了真正的异步 IO。在 Linux 系统下,异步 IO 模型在 2.6 版本才引入,JDK 对它的支持目前并不完善,因此异步 IO 在性能上没有明显的优势。大多数高并发服务端的程序都是基于 Linux 系统的。因而,目前这类高并发网络应用程序的开发大多采用 IO 多路复用模型。Netty 框架使用的就是 IO 多路复用模型,而不是异步 IO 模型。


用户头像

wzh

关注

还未添加个人签名 2019-06-13 加入

还未添加个人简介

评论

发布
暂无评论
io模型_Linux_wzh_InfoQ写作社区