BIO、NIO、AIO 与 IO 多路复用:select、poll、epoll 详解

引言

高并发服务的瓶颈,很多时候不在业务逻辑,而在 IO 处理方式。如果一个线程只能盯着一个连接等数据,那连接数一上来,系统资源很快就会被耗尽。

所以理解 BIO、NIO、AIO 和 IO 多路复用,本质上是在理解:

"一个程序如何高效地同时处理大量连接。"

BIO:同步阻塞 IO

BIO 的特点很直接:

  • 同步
  • 阻塞

调用线程在执行读写操作时,会一直等待,直到数据准备好或者操作完成。

可以把它理解成:

"一个人盯着一个水壶,水不开就一直等。"

优点是模型简单,编码直观;缺点是线程利用率低,连接一多就很难扛住。

NIO:同步非阻塞 IO

NIO 的关键是"非阻塞"。

当应用进程发起读操作时,如果内核数据还没有准备好,不会一直卡住线程,而是立刻返回。程序可以先去做别的事,稍后再回来继续处理。

这可以理解成:

"一个人不停地轮询很多个水壶,哪个烧开了就处理哪个。"

NIO 通常会配合缓冲区和选择器一起使用。

NIO 的典型流程

  1. 服务端通过 ServerSocketChannel 接收连接。
  2. SocketChannel 注册到 Selector 上。
  3. Selector 监听多个通道的就绪事件。
  4. 某个通道可读或可写时,应用程序再去处理。
  5. 处理完成后继续等待下一轮事件。

NIO 的核心价值是:

"一个线程可以管理多个连接,而不是一个线程只服务一个连接。"

NIO 工作流程示意图如下:

AIO:异步非阻塞 IO

AIO 比 NIO 更进一步。

NIO 虽然不阻塞,但通常仍需要应用程序主动轮询或处理就绪事件;AIO 则是由系统在 IO 完成后主动通知应用。

还是用烧水类比:

  • NIO:你不断去看哪些水壶烧开了
  • AIO:水壶烧开后主动叫你

AIO 的理论上限更高,但不同平台的支持成熟度和实现复杂度不完全一致,实际工程里 NIO 和基于多路复用的模型更常见。

什么是 IO 多路复用

IO 多路复用的目标是:

"让一个线程同时监听多个文件描述符,当其中某些就绪时再处理。"

常见实现有三种:

  1. select
  2. poll
  3. epoll

它们本质上都在做同一件事:帮助应用进程找到"哪些连接现在可以读写"。

select 的机制

select 的核心思路是使用位图结构记录要监听的文件描述符。

基本流程如下:

  1. 应用程序准备一个 fd_set 集合。
  2. 调用 select,把集合从用户态拷贝到内核态。
  3. 内核检查哪些文件描述符已经就绪。
  4. 返回后,应用程序遍历整个集合,找出可读可写的描述符并处理。

select 的缺点

  • 单次可监听的 fd 数量有限,常见上限是 1024
  • 每次调用前都要重新设置监听集合
  • 用户态和内核态之间有拷贝开销
  • 每次返回后都要线性扫描所有 fd,时间复杂度是 O(N)

select 多路复用机制示意图如下:

poll 的机制

pollselect 思路类似,但不再使用固定大小的位图,而是使用 pollfd 数组。

它解决了 select 的一个明显问题:

  • 不再受 1024 个 fd 的硬限制

但它仍然有两个主要问题:

  • 用户态到内核态仍然需要拷贝
  • 返回后仍然要遍历整个描述符集合,复杂度仍是 O(N)

所以 pollselect 更灵活,但没有本质改变扫描成本。

poll 多路复用机制示意图如下:

epoll 的机制

在 Linux 下,epoll 是高并发网络编程的核心能力之一。

它的典型流程是:

  1. epoll_create 创建一个 epoll 实例
  2. epoll_ctl 注册、修改或删除要监听的 fd
  3. epoll_wait 等待就绪事件

epoll 的关键优势在于:

  • 监听集合和就绪集合分离
  • 不需要每次都把所有 fd 从用户态重新传给内核
  • 返回时给出的就是就绪事件列表

因此实际处理时,只需要遍历已经就绪的那些 fd,而不是扫描全部 fd。

epoll 的优点

  • 没有 select 的 1024 限制
  • 不需要每次重置整个监听集合
  • 大量连接场景下性能更好
  • 更适合高并发服务器

epoll 多路复用机制示意图如下:

三者对比

可以把三者区别总结成一句话:

  • select:能用,但连接数和扫描成本都比较受限
  • poll:去掉了 fd 数量限制,但扫描问题仍在
  • epoll:更适合海量连接场景,只关注真正就绪的事件

如何理解"同步"和"阻塞"

这两个词很容易混淆。

阻塞 vs 非阻塞

说的是当前线程在系统调用时会不会被卡住等待。

同步 vs 异步

说的是 IO 完成后,由谁来负责拿结果。

  • 同步:应用自己主动处理结果
  • 异步:系统完成后主动通知应用

总结

IO 模型这部分,最重要的不是背定义,而是记住下面这个演进逻辑:

  1. BIO:一个线程盯一个连接,简单但浪费
  2. NIO:线程不阻塞,可以管理多个连接
  3. IO 多路复用:帮助线程高效发现哪些连接就绪
  4. epoll:Linux 高并发场景下最常见的选择
  5. AIO:由系统在完成后主动通知,更偏异步

如果你后面准备继续学 Netty、Reactor 模型,这一篇就是基础前置。


如果这篇文章对你有帮助,欢迎继续阅读本系列后续内容。若文中有不准确或需要补充的地方,也欢迎指出。

相关推荐
qq_206901397 分钟前
为什么宝塔面板网站无法正常连接外部远程数据库_检查服务器安全组放行端口并开启IP授权
jvm·数据库·python
亚空间仓鼠9 分钟前
关系型数据库MySQL(二):高级特性
数据库·sql·mysql
A-Jie-Y30 分钟前
JAVA框架-SpringBoot环境搭建指南
java·spring boot
亚空间仓鼠30 分钟前
关系型数据库MySQL(五):Galara高可用
数据库·mysql
深兰科技38 分钟前
深兰科技与淡水河谷合作推进:矿区示范加速落地
java·人工智能·python·c#·scala·symfony·深兰科技
weixin_586061461 小时前
JavaScript中Redux-Thunk处理异步Action的任务流
jvm·数据库·python
C^h1 小时前
rtthread控制达妙4310电机
数据库·单片机·嵌入式硬件
晴天¥1 小时前
达梦数据库共享存储集群搭建(DSC双节点+Openfiler-IP SAN存储)
linux·数据库·达梦数据库
码界奇点1 小时前
基于Spring Boot的前后端分离商城系统设计与实现
java·spring boot·后端·java-ee·毕业设计·源代码管理
一叶飘零_sweeeet1 小时前
深度剖析:Java 并发三大量难题 —— 死锁、活锁、饥饿全解
java·死锁·活锁·饥饿