核心要点速览

  • 五大 IO 模型:阻塞 IO(低并发)、非阻塞 IO(忙等)、IO 多路复用(高并发核心)、信号驱动 IO(极少用)、异步 IO(理想模型)
  • IO 多路复用:select(位图,FD 上限 1024)、poll(动态数组,轮询)、epoll(Linux 首选,O (1) 事件驱动)
  • 同步 vs 异步:同步需等待 IO 就绪 / 完成(阻塞 / 非阻塞 / IO 多路复用),异步无需等待(内核回调通知)
  • 高并发模型:Reactor(事件驱动)、多线程 Reactor(主线程 epoll + 子线程池处理任务)
  • 核心选择:百万级并发选「epoll + 线程池 + ET 模式」,中高并发选「epoll/poll + 有限线程」,低并发选「BIO + 线程池」

一、同步 IO 与异步 IO

核心定义

  • 同步 IO:线程发起 IO 请求后,必须等待 IO 操作(就绪或数据拷贝)完成才能继续执行,线程主动参与等待过程。
    • 典型:阻塞 IO、非阻塞 IO、IO 多路复用(select/poll/epoll)。
  • 异步 IO:线程发起 IO 请求后,无需等待,可直接执行其他任务;内核完成 “数据拷贝” 后,通过回调 / 信号通知线程处理结果。
    • 典型:POSIX AIO、Windows IOCP。

关键差异

  • 同步 IO 的核心是 “线程等待 IO 就绪 / 完成”,即使非阻塞 IO 的轮询,线程仍在主动消耗 CPU;
  • 异步 IO 的核心是 “线程不参与 IO 等待和数据拷贝”,完全由内核处理,效率最高但兼容性差。
  • 易错点:IO 多路复用是同步 IO(需线程主动读取就绪数据,并非内核自动推送)。

二、五大 IO 模型详解

IO 操作核心流程:「发起 IO 请求→等待 IO 就绪→数据拷贝」,模型差异集中在 “等待就绪” 和 “数据拷贝” 的实现方式。

1. 阻塞 IO(BIO):最简单的低并发模型

  • 原理:线程发起 IO 请求(如recv/accept)后,内核阻塞线程,直到数据拷贝完成(从网卡→用户缓冲区)才唤醒线程。
  • 流程:发起请求→内核阻塞线程→数据拷贝完成→线程唤醒返回。
  • 优点:实现简单,无需处理非阻塞逻辑,开发成本低。
  • 缺点:1 线程只能处理 1 连接,并发能力极差(线程阻塞期间无法做其他事)。
  • 适用场景:连接数少(千级以下)、逻辑简单的场景(如本地工具、小规模服务)。

2. 非阻塞 IO(NIO):轮询式低效模型

  • 原理:通过fcntl设置 Socket 为非阻塞,线程发起 IO 请求后,内核立即返回(无论数据是否就绪);数据未就绪时返回EAGAIN,线程需主动轮询重试。
  • 流程:发起请求→数据未就绪→返回EAGAIN→轮询重试→数据就绪→数据拷贝→返回结果。
  • 优点:线程不阻塞,理论可处理多个连接。
  • 缺点:轮询导致 CPU 空转,资源利用率极低,极少单独使用。
  • 适用场景:配合 IO 多路复用使用(避免单独轮询的 CPU 浪费)。

3. IO 多路复用:高并发核心模型

  • 原理:通过 “中间组件”(select/poll/epoll)管理多个 Socket(FD),线程阻塞在中间组件上,而非单个 IO 请求;任一 FD 就绪时,中间组件通知线程处理该 FD 的 IO。
  • 流程:注册 FD 到中间组件→线程阻塞在epoll_wait/select→FD 就绪→中间组件返回就绪 FD→线程处理 IO(recv/send)。
  • 核心优势:单线程 / 少量线程处理大量连接(避免线程阻塞在单个 IO),CPU 利用率高。

三种实现对比

实现 底层结构 最大 FD 限制 效率 核心缺陷
select 位图(bitmask) 1024(固定) O (n)(轮询) FD 上限低、轮询开销大(高并发卡顿)
poll 动态数组(fdset) 无(理论) O (n)(轮询) 轮询开销大,无 FD 上限但高并发低效
epoll 红黑树 + 就绪链表 无(系统限制) O (1)(事件驱动) 仅支持 Linux(平台依赖)

epoll 核心优化

  1. 事件驱动而非轮询:内核通过回调机制记录就绪 FD,无需遍历所有注册 FD,效率 O (1)。
  2. 共享内存:FD 注册信息存储在内核态,避免用户态与内核态频繁数据拷贝。
  3. 支持两种触发模式
    • 水平触发(LT,默认):FD 缓冲区有数据则持续通知,易用不易漏数据(适合大多数场景)。
    • 边缘触发(ET):仅数据 “首次到来” 时通知一次,需一次性读完缓冲区数据,效率更高(适合高并发)。

4. 信号驱动 IO:极少使用的模型

  • 原理:线程通过sigaction注册SIGIO信号回调,发起 IO 请求后不阻塞;IO 就绪时,内核发送信号,线程在回调中处理 IO。
  • 优点:无需轮询,CPU 利用率高。
  • 缺点:信号处理逻辑复杂(竞态、嵌套),调试困难,实际场景极少使用。

5. 异步 IO:理想但难用的模型

  • 原理:线程发起 IO 请求时指定回调函数,立即返回执行其他任务;内核完成 “数据拷贝” 后,调用回调函数通知线程。
  • 流程:发起aio_read(指定回调)→线程执行其他任务→内核完成数据拷贝→触发回调。
  • 优点:线程完全不参与 IO 等待和数据拷贝,并发效率最高。
  • 缺点:跨平台支持差(Linux AIO 不成熟,Windows IOCP 常用),开发复杂度高。
  • 适用场景:Windows 高并发服务(如游戏服务器),Linux 场景下极少用。

三、高并发核心模型:Reactor 模式

1. 核心原理

  • 事件驱动架构:通过epoll监控所有 IO 事件(连接、读、写),事件就绪后分发到对应的处理器(连接处理器、读处理器、写处理器)。
  • 核心组件:
    • 事件多路分发器:epoll,负责监控 FD 事件。
    • 事件处理器:处理具体事件(如accept新连接、recv数据、send响应)。
    • 事件队列:存储就绪事件,供分发器调度。

2. 多线程 Reactor(生产环境首选)

  • 架构:主线程负责epoll事件监控和连接建立(accept),新连接分配给子线程池;子线程处理 IO 读写和业务逻辑。
  • 优势
    • 主线程不处理业务,避免 IO 阻塞影响事件监控。
    • 线程池复用线程,降低线程创建 / 切换开销。
    • 支持百万级并发(epoll管理 FD + 线程池处理业务)。

四、高并发瓶颈与 IO 模型选择

1. 核心瓶颈

  • 线程阻塞:BIO 模型中线程阻塞在 IO,限制并发数。
  • 线程切换开销:多线程模型中,大量线程切换消耗 CPU 资源。
  • 内核态 / 用户态拷贝:频繁数据拷贝(如 select 的 FD 集合拷贝)降低效率。

2. 分场景 IO 模型选择

并发量级 推荐模型 核心原因
百万级(如 Web/IM) epoll(ET 模式)+ 多线程 Reactor + 线程池 epoll O (1) 效率,ET 模式减少通知,线程池处理业务
万级(中高并发) epoll/poll + 有限线程 平衡资源开销与并发能力,无需复杂架构
千级以下(低并发) BIO + 线程池 开发简单,无需复杂 IO 管理

3. 高并发优化策略

  • 采用 epoll ET 模式:减少事件通知次数,需一次性读完缓冲区数据。
  • 非阻塞 IO 配合 ET 模式:避免 IO 操作阻塞线程。
  • 线程池隔离业务逻辑:主线程仅处理 IO 事件,业务逻辑交给子线程。
  • 减少数据拷贝:使用内存池、零拷贝技术(如sendfile)。

五、问答

1. epoll 的 LT 和 ET 模式有什么区别?

  • LT(水平触发):FD 缓冲区有数据则持续通知,直到数据读完;易用,不会漏数据,默认模式。
  • ET(边缘触发):仅数据 “首次到来” 时通知一次,需一次性读完缓冲区;效率高,减少通知次数,适合高并发,但需处理非阻塞 IO 避免漏读。

2. 为什么 IO 多路复用是同步 IO 而非异步 IO?

因为 IO 多路复用仅解决 “等待 IO 就绪” 的阻塞问题,线程仍需主动调用recv/send完成数据拷贝(IO 操作的核心步骤);异步 IO 的核心是内核自动完成数据拷贝并通知线程,线程无需参与 IO 操作。

3. epoll 比 select/poll 高效的原因是什么?

  • 事件驱动而非轮询:无需遍历所有注册 FD,仅处理就绪事件。
  • 共享内存:FD 注册信息存储在内核,避免用户态与内核态频繁拷贝。
  • 无 FD 上限:红黑树存储 FD,支持海量连接(仅受系统资源限制)。