目录
一、I/O 模型简介
二、I/O 模型
2.1 同步阻塞 I/O
2.2 同步非阻塞I/O
2.3 I/O多路复用
2.4 异步I/O
2.5 信号驱动 I/O
三、总结
一、I/O 模型简介
所谓的 I/O 就是计算机内存与外部设备之间拷贝数据数据的过程。有 5 中 I/O 模型,分别是同步阻塞 I/O、同步非阻塞 I/O、多路复用I/O、信号驱动 I/O 和异步 I/O。为什么需要这么多的 I/O 模型呢?
CPU 访问内存的速度远远高于外部设备,因此 CPU 是先把外部设备的数据读取到内存里,然后进行处理。当你的程序向外部设备发起一个读指令时,数据从外部设备拷贝到内存里需要一段时间才能完成,这个时候 CPU 做什么工作呢?是让 CPU 处理其他工作,还是让 CPU 不停地去查数据?这就是 I/O 模型要解决的问题,这里来探讨下各种网络模型。
二、I/O 模型
对于一个网络 I/O 的通信过程,比如网络数据读取,会涉及两个对象,一个是调用这个操作的用户线程,一个是操作系统的内核。一个进程的地址空间分为用户空间和内核空间,用户线程不能直接访问内核空间。
当用户发起一个网络数据读取的 I/O 操作后,会经历以下两步:
- 用户线程等待内核将数据从网卡拷贝到内核空间
- 内核将数据从内核空间拷贝到用户空间
各种 I/O 模型的区别就是他们实现这两步的方式不一致。
2.1 同步阻塞 I/O
同步阻塞 I/O (BIO) 是一种最基本的 I/O 模型,也是最常见的 IO 处理方式。在同步阻塞 I/O 模型中,当一个进程或线程发起一个 I/O 请求,该进程或线程会一直阻塞着,直到请求完成为止。
比如读取网络数据,用户线程发起 reda 调用后就阻塞了,内核等待网卡数据到来,把数据从网卡拷贝到内核空间,再把用户线程唤醒。
同步阻塞 I/O 模型相对实现比较简单,易于理解和编码,在低并发场景下,可以保证数据的及时处理和响应。但在高并发场景下效率低下,因大量线程可能被阻塞,导致CPU资源浪费,难以扩展到大量并发并发连接的情况,创建大量线程上下文切换开销大,容易出现性能瓶颈。
2.2 同步非阻塞I/O
同步非阻塞 I/O(Non-Blocking IO)是一种 IO 模型,用户线程不断的发起 read 调用,数据没到内核空间时,每次都返回失败直到数据到了内核空间,这一次 read 调用后,在等待数据从内核空间拷贝到用户空间这段时间里,现成还是阻塞的,等数据到了用户空间在把数据唤醒。
在 BIO 中,调用者在等待I/O操作完成之前不能做任何事情,而在同步非阻塞 I/O 中,调用者可以继续执行其他任务而无需等待 I/O 操作完成。
同步非阻塞 I/O 具有以下特点:
- 尽管 I/O 操作是非阻塞的,但应用程序仍然负责检查 I/O 操作的状态,因此从某种意义上来说,这种模型仍然是同步的,因为需要应用程序主动查询结果。
- 线程不会因为等待 I/O 操作而被阻塞,从而可以继续执行其它任务,提高了线程利用率。
- 应用程序必须定期检查 I/O 操作是否可以继续进行,可以通过轮询实现。
- 与 BIO 相比实现比较复杂,因为要求应用程序管理 I/O 操作的状态和重试逻辑。
2.3 I/O多路复用
I/O 多路复用(I/O Multiplexing)是一种高效地处理多个 I/O 流的方法,允许一个单一的线程管理多个文件描述符(如网络套接字、文件、管道等)的 I/O 操作。它克服了同步阻塞 I/O 和同步非阻塞 I/O 的局限性,特别是当需要处理大量并发连接时,能极大地提高系统的响应性和资源利用率。
在 I/O 多路复用中,操作系统提供了一种机制,可以监视多个文件描述符并在其中一个或多个描述符准备就绪时通知应用程序。这样,应用程序就可以在一个循环中处理多个 I/O 流,而不需要为每个流维护一个独立的线程。
用户线程的读取操作分为两步,线程先发起 select 调用,目的是问内核数据准备好了没有?等内核把数据准备好了,用户线程在发起 Read 调用。在等到数据从内核拷贝到用户空间这段时间内,线程还是阻塞的。多路复用的意思是,一次 select 调用可以向内核查多个数据通道(channel)的状态,所以叫多路复用。
常见的 I/O 多路复用技术
- select():这是最古老的多路复用函数,它允许一个进程监控多个文件描述符,等待一个或多个描述符变为可读、可写或发生异常。select() 有最大描述符数量的限制,并且数量随着描述符的增加而降低。
- poll():类似于 select(),但是没有描述符数量的限制。poll() 使用链表存储描述符,因此在描述符数量过多时,效率比 select() 高
- epoll():这时一个更高效率的多路复用函数,专门为高并发设计。epoll() 使用事件驱动的方式,可以注册、修改和删除感兴趣的事件,而且效率不随描述符的增加而显著降低。
I/O 多路复用可以使用较少的线程甚至单线程来处理大量并发连接,减少了线程上下文切换的开销。能够迅速响应 I/O 事件,提高了整体响应速度。适用于高并发场景。
I/O 多路复用的例子很多,比如 Redis 中使用多路复用来处理客户端的链接和请求、Tomcat中的 NioEndPoint 使用多路复用技术、Nginx 中也使用到了多路复用技术等等。
2.4 异步I/O
异步 I/O(Asynchronous I/O)允许应用程序发起 I/O 请求后立即返回控制流,而无需等待I/O 操作完成。异步 I/O 的设计目标是为了提高系统的响应性和效率,尤其是当 I/O 操作可能非常耗时(如磁盘读写、网络通信)时。
异步 I/O 的特点如下:
- 非阻塞:应用程序在发起 I/O 请求后不会立即被阻塞,可以立即返回并执行其它操作。
- 事件驱动:I/O 操作完成通过事件通知给应用程序,应用程序通过事件循环或回调函数来响应这些事件。
- 并行性:应用程序可以同时发起多个 I/O 请求,而无需等待前一个请求完成。
- 效率高:通过避免阻塞,可以有效的利用计算资源,提高系统整体的响应能力。
2.5 信号驱动 I/O
信号驱动I/O(Signal-driven I/O)是一种特殊的I/O模型,主要用于Unix和类Unix系统中。在这种模型下,当一个I/O操作准备就绪时,内核会向应用程序发送一个信号,告知它可以进行I/O操作了。信号驱动I/O主要在处理网络I/O时使用,特别适用于套接字(socket)操作。
三、总结
每种 I/O 模型的选择取决于应用程序的具体需求,包括预期的并发级别、性能要求、资源使用效率和开发复杂度。在实际应用中,开发人员可能会根据系统的需求和约束,混合使用上述模型以达到最佳效果。例如,一个高性能的 Web 服务器可能会使用 I/O 多路复用来处理网络连接,同时在内部数据处理中使用异步 I/O 来加速文件读写。
往期经典推荐:
深入浅出 Spring @Async 异步编程的艺术-CSDN博客
Sentinel与Nacos强强联合,构建微服务稳定性基石的重要实践_sentinel 整合nacos-CSDN博客
云原生基石:解码Docker镜像分层-CSDN博客
手把手教你实现服务高可用性_服务高可用方案-CSDN博客
Redis高性能的秘密原来在这,超越想象-CSDN博客