Linux 五种 IO 模型
Linux有以下五种IO模型,前四种都是同步的,只有最后一种才是异步 IO。
- 阻塞 IO (Blocking IO)
- 非阻塞 IO (Non-blocking IO)
- 多路复用 IO (IO multiplexing)
- 信号驱动 IO (signal driven I/O (SIGIO))
- 异步 IO (asynchronous I/O)
阻塞 I/O(Blocking IO):
如图所示,IO 过程分为 (1)内核数据准备 和 (2)数据从内核空间拷贝到用户空间 这两个阶段。在用户线程发起 IO 调用后,用户线程在这两个阶段全程等待,直到有结果返回。这种 IO 就是阻塞 IO 模型。
- 在阻塞 I/O 模型中,当应用程序发起一个 I/O 操作时,它会被阻塞直到该操作完成。这意味着应用程序无法执行其他任务,直到 I/O 操作完成并返回结果。
- 阻塞 I/O 适用于简单的应用场景,但在高并发环境下会导致性能问题,因为一个阻塞的 I/O 操作会阻塞整个程序。
非阻塞 I/O(Non-blocking IO):
非阻塞 IO 模型下,在用户线程发起 IO 调用后,在内核数据还未准备好时, IO 函数可立即返回。用户线程通过多次调用 IO 函数来检查内核数据是否准备好(轮询),当检查到数据准备好后,执行第二阶段,将数据从内核空间拷贝到用户空间,这一阶段用户线程是阻塞的。
- 非阻塞 I/O 模型允许应用程序在发起 I/O 操作后继续执行其他任务,而不会被阻塞等待 I/O 操作的完成。应用程序可以周期性地查询或轮询 I/O 操作的状态,以确定操作是否完成。
- 非阻塞 I/O 可以提高并发性能,但需要应用程序不断地查询 I/O 状态,这可能导致 CPU 的浪费。
多路复用 I/O(IO multiplexing):
用户线程通过 select、poll、epoll 等函数 阻塞式地监听多路 IO 事件,直到有一个或多个 IO 操作处于就绪状态(有数据可读或可写)时返回,开始执行 I/O 操作。多路复用模型的特点是可以通过一个线程处理多路 IO 请求。
- 多路复用 I/O 模型使用操作系统提供的多路复用机制,例如 select、poll 或 epoll,来同时监视多个 I/O 操作的状态。当有任何一个 I/O 操作准备就绪时,应用程序可以进行读取或写入操作。
- 多路复用 I/O 可以同时处理多个 I/O 操作,而无需为每个操作创建一个线程或进程,从而提高了系统的可扩展性和性能。
信号驱动 I/O(Signal-Driven I/O):
用户线程发起 IO 操作后,可立即返回不阻塞。等到内核数据准备完成后,通过信号函数通知用户线程,执行第二阶段数据拷贝操作。
- 信号驱动 I/O模型,它使用信号机制来通知应用程序有关 I/O 操作的完成。在信号驱动 I/O 中,应用程序通过设置信号处理函数,并在发起 I/O 操作后继续执行其他任务。当 I/O 操作完成时,操作系统会发送一个信号给应用程序,以通知操作的结果。
异步 I/O(asynchronous I/O):
异步 IO 的特点是用户线程发起 IO 请求后立即返回,在 IO 操作的两个阶段全程都不阻塞,不需要用户线程参与,直到最后数据拷贝完成后,内核发送信号通知用户线程处理完毕。
- 异步 I/O 模型中,应用程序发起一个 I/O 操作后,不需要等待操作完成。相反,应用程序可以继续执行其他任务,并通过回调函数或事件通知的方式处理 I/O 操作的完成。
- 异步 I/O 可以提高系统的并发性和可扩展性,因为应用程序无需为每个 I/O 操作创建线程或进程,并可以处理更多的并发请求。
五种IO模型比较
异步 I/O 模型的发展技术是: select -> poll -> epoll -> aio -> libevent -> libuv。
Java中的四种IO模型
Unix 中的五种 IO 模型,除信号驱动模型外,Java 对其它四种 IO 模型都有支持。
阻塞 I/O(Blocking I/O)
- 在阻塞 I/O 模型中,当应用程序执行 I/O 操作时,它会被阻塞,直到操作完成。
- Java中的阻塞 I/O 是基于流(Stream)的 I/O,例如
InputStream和OutputStream类以及其子类。调用阻塞 I/O 的读取方法(如read())或写入方法(如write())会阻塞当前线程,直到数据可读取或可写入
非阻塞 I/O(Non-blocking I/O):
- 在非阻塞 I/O 模型中,应用程序可以立即返回并继续执行其他任务,而不会等待 I/O 操作的完成。
- Java中的非阻塞 I/O 是基于通道(Channel)的 I/O,例如
SelectableChannel和SocketChannel类。通过调用非阻塞通道的configureBlocking(false)方法,可以设置通道为非阻塞模式。然后可以使用Selector类来进行多路复用,以监视多个通道的状态,并在通道就绪时进行相应的读取或写入操作。
多路复用 I/O
通过NIO的 Selector 和 SelectionKey实现
异步IO
通过NIO的 CompletionHandler 和 AsynchronousChannel实现
Java 中传统的 IO 都是阻塞 IO,比如通过 socket 来读数据,调用 read 方法之后,如果数据没有就绪,当前线程就会一直阻塞在 read 方法调用那里,直到有数据才返回。因此在传统的网络服务设计模式中,比较经典的模式是多线程或线程池。当一条线程正在处理一个 client 的请求时,它是阻塞的状态,这时如果又来了一个 client 的请求,只好再启动一个新的线程去服务它,一个线程的阻塞不会影响其他线程工作。这种服务方式虽然看起来简便,但服务器为每个 client 都启动一个线程,资源消耗非常大。线程池的方式使得线程可以重复使用,在一定程度上减少了创建和销毁线程的系统开销。 多线程(或线程池)模式适用于大部分的连接为短连接的情景。如果大部分的连接都为长连接,而同一时刻只有少数的连接中存在 IO 操作,那么为每一个连接安排一个线程的服务方式是非常浪费的。因此便出现了Java NIO 模型。