IO模型

概念理解

  • 从计算机结构的视角来看的话, I/O描述了计算机系统与外部设备之间通信的过程。
  • 我们在平常开发过程中接触最多的就是 磁盘 IO(读写文件) 和 网络 IO(网络请求和相应)。
  • 从应用程序的视角来看的话,我们的应用程序对操作系统的内核发起 IO 调用(系统调用),
    操作系统负责的内核执行具体的 IO 操作。

也就是说,我们的应用程序实际上只是发起了IO操作的调用而已,具体IO的执行是由操作系统的内核来完成的。

当应用程序发起 I/O 调用后,会经历两个步骤:

  • 内核等待 I/O 设备准备好数据 (操作系统将外部数据加载到内核缓冲区)
  • 内核将数据从内核空间拷贝到用户空间(操作系统将数据从内核缓冲区拷贝到进程缓冲区)

阻塞/非阻塞与同步/非同步

  • 阻塞/非阻塞:阻塞和非阻塞强调的是进程对于操作系统IO是否处于就绪状态的处理方式。
    进程发起了读取数据的IO调用,操作系统需要将外部数据拷贝到进程缓冲区,
    在有数据拷贝到进程缓冲区前,进程缓冲区处于不可读状态,我们称之为操作系统IO未就绪。

    如果操作系统IO处于未就绪状态,当前进程或线程如果一直等待直到其就绪,该种IO方式为阻塞IO。
    如果进程或线程并不一直等待其就绪,而是可以做其他事情,这种方式为非阻塞IO。
    所以对于非阻塞IO,我们编程时需要经常去轮询就绪状态。

  • 同步/非同步:同步和异步描述的是针对当前执行线程、或进程而言,发起IO调用后,
    当前线程或进程是否挂起等待操作系统的IO执行完成。

    在操作系统将外部数据写入进程缓冲区这个期间,进程或线程挂起等待操作系统IO执行完成的话,
    这种IO执行策略就为同步,如果进程或线程并不挂起而是继续工作,这种IO执行策略便为异步。

  • 自己的理解
    如果进程发出IO请求后,等待操作系统将外部数据读取到内核空间,则被称为阻塞,否则被称为非阻塞

    如果进程发出IO请求后,进程并不挂起等待而是直接执行其他,则为异步,这种方式跳过了
    操作系统将外部数据读取到内核空间步骤,并且跳过了操作系统将内核空间数据拷贝到用户空间的步骤

IO模型

IO模型指的是应用进程对待操作系统IO状态的不同处理方式。
在IO调用时,对待操作系统IO就绪状态的不同方式,决定了其是阻塞或非阻塞模式。
在IO执行时,线程或进程是否挂起等待IO执行决定了其是否为同步或异步IO。

UNIX 系统下, IO 模型一共有 5 种:
同步阻塞 I/O同步非阻塞 I/OI/O 多路复用信号驱动 I/O异步 I/O,其中,前四个被称为同步IO。

Java中3种常见的IO模型

  • BIO

    BIO 属于同步阻塞 IO 模型
    由上我们可知,这种IO模型应用进程发起IO调用(系统调用,read调用)后,应用进程便一直阻塞
    直到系统内核IO处理完毕后,将数据拷贝到应用进程的用户空间为止。

    在客户端连接数量不高的情况下,是没问题的。但是,当面对十万甚至百万级连接的时候,
    传统的 BIO 模型是无能为力的。因此,我们需要一种更高效的 I/O 处理模型来应对更高的并发量。

  • NIO

    Java中的NIO于Java1.4中引入,对应 java.nio 包,提供了 Channel ,SelectorBuffer等抽象。
    NIO中的N可以理解为 Non-blocking,不单纯是 New。它支持面向缓冲的,基于通道的I/O操作方法。
    对于高负载、高并发的(网络)应用,应使用NIO。

    Java中的NIO是I/O多路复用模型,并不是同步非阻塞 IO 模型。

    先看看同步非阻塞IO模型,由上我们知道:

    • 非阻塞意味着,当应用程序发起IO请求后,如果操作系统IO处于未就绪状态,
      则应用程序并不会等待操作系统IO就绪,
    • 而同步则意味着,应用程序发起IO调用,当前线程或进程需要等待操作系统完成IO工作并告知进程
      已经完成,线程或进程才能继续往下执行其他既定指令。

    同步非阻塞 IO 模型中,应用程序会一直发起 read 调用,等待数据从内核空间拷贝到用户空间的
    这段时间里,线程依然是阻塞的,直到在内核把数据拷贝到用户空间。

    相比于同步阻塞 IO 模型,同步非阻塞 IO 模型确实有了很大改进。通过轮询操作,避免了一直阻塞。

    但是,这种 IO 模型同样存在问题:
    应用程序不断进行 I/O 系统调用轮询数据是否已经准备好的过程是十分消耗 CPU 资源的。

    I/O 多路复用模型

    IO 多路复用模型中,线程首先发起 select 调用,询问内核数据是否准备就绪,等内核把数据准备好了,
    用户线程再发起 read 调用。read 调用的过程(数据从内核空间->用户空间)还是阻塞的。

    IO 多路复用模型,通过减少无效的系统调用,减少了对 CPU 资源的消耗。

    Java 中的NIO,有一个非常重要的选择器 ( Selector ) 的概念,也可以被称为 多路复用器
    通过它,只需要一个线程便可以管理多个客户端连接。当客户端数据到了之后,才会为其服务。

  • AIO

    AIO也就是NIO2。Java7中引入了NIO的改进版NIO2,它是异步IO模型。
    异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,
    当后台处理完成,操作系统会通知相应的线程进行后续的操作。