目录1. 五种IO概念1.0 同步与异步 I/O 的核心界定1.1 阻塞 I/O 模型Blocking I/O1.2 非阻塞 I/O 模型Non-blocking I/O1.3 I/O 多路复用模型I/O Multiplexing1.4 信号驱动 I/O 模型Signal-driven I/O1.5 异步 I/O 模型Asynchronous I/O1.6 五种 I/O 模型核心对比2. 五种IO模型2.1 阻塞IO2.2 非阻塞IO2.3 IO多路转接2.4 信号驱动IO2.5 异步IO3. 高级IO重要概念3.1 同步通信 vs 异步通信(synchronous communication/ asynchronous3.1.1 概念3.1.2 易混淆概念区分两种「同步」的本质差异3.2 阻塞 vs 非阻塞3.3 阻塞 / 非阻塞 与 同步 / 异步的核心区分3.4 其他高级IO4. 非阻塞IO4.1 fcntl4.1.1 核心实操将文件描述符设为非阻塞模式1. 五种IO概念在类 Unix/Linux 系统中经典的五种 I/O 模型分别是阻塞 I/O、非阻塞 I/O、I/O 多路复用、信号驱动 I/O、异步 I/O。I/O 操作的核心分为两个关键阶段等待数据就绪内核等待网络 / 磁盘数据加载到内核缓冲区、数据复制将内核缓冲区的数据拷贝到用户进程的缓冲区。不同模型的核心差异就体现在这两个阶段进程是否阻塞、如何等待。1.0 同步与异步 I/O 的核心界定在 POSIX 标准中同步 I/O与异步 I/O的判定依据是数据复制阶段进程是否阻塞。同步 I/O数据复制时进程处于阻塞状态包括阻塞 I/O、非阻塞 I/O、I/O 多路复用、信号驱动 I/O。异步 I/O等待数据 数据复制全程由内核后台完成进程全程不阻塞。1.1 阻塞 I/O 模型Blocking I/O工作原理进程发起 I/O 系统调用如recvfrom读取网络数据后会立即被操作系统挂起进入阻塞状态。内核会完整执行等待数据就绪和数据复制两个阶段直到 I/O 操作全部完成才会将结果返回给进程进程解除阻塞继续执行后续逻辑。执行流程用户进程调用recvfrom→ 内核等待数据 → 数据就绪内核拷贝数据至用户空间 → 系统调用返回进程恢复运行。特点与适用场景优点实现逻辑简单代码编写和调试成本低是入门级网络编程的默认模型。缺点并发能力极差。一个线程只能处理一个 I/O 连接高并发场景下需要创建大量线程带来巨大的内存和线程切换开销。适用场景并发量极低的简单单机程序、小型工具类服务。1.2 非阻塞 I/O 模型Non-blocking I/O工作原理进程设置文件描述符为非阻塞模式后发起 I/O 调用。若内核数据未就绪内核会立即返回错误码如EWOULDBLOCK进程不会阻塞可执行其他任务。进程需要通过轮询的方式反复发起 I/O 调用直到数据就绪。数据就绪后进程再次调用 I/O 接口此时会阻塞等待数据复制完成。执行流程进程循环调用recvfrom→ 内核返回未就绪错误 → 进程执行其他任务 → 重复轮询 → 数据就绪调用阻塞完成数据拷贝 → 系统调用返回。特点与适用场景优点进程在等待数据阶段不阻塞可以充分利用 CPU 处理其他任务。缺点轮询会持续占用 CPU 资源连接数越多CPU 消耗越严重高并发场景下效率极低。适用场景几乎不单独使用通常作为 I/O 多路复用模型的基础技术组件。1.3 I/O 多路复用模型I/O Multiplexing工作原理进程不直接监听单个 I/O 事件而是通过select/poll/epoll等系统调用将多个需要监控的文件描述符网络 Socket、文件句柄等注册到内核。内核同时监控所有描述符的状态当任意一个描述符数据就绪系统调用就会返回。进程再遍历就绪的描述符发起 I/O 调用完成数据复制此阶段仍会阻塞。主流实现对比实现方式核心特点缺陷select支持跨平台有文件描述符数量限制默认 1024需要遍历所有描述符效率随描述符数量增加急剧下降poll移除了文件描述符的数量限制同样需要线性遍历所有描述符高并发场景性能瓶颈明显epollLinux 专属高效实现无描述符数量限制采用事件触发机制仅支持 Linux 系统跨平台性差特点与适用场景优点单个线程可以管理海量 I/O 连接大幅减少线程数量和切换开销是高并发网络服务的主流方案。缺点数据复制阶段进程仍会阻塞本质仍属于同步 I/O。适用场景高并发网络服务器如Nginx、Redis、Java NIO等主流中间件和服务。1.4 信号驱动 I/O 模型Signal-driven I/O工作原理进程通过系统调用向内核注册 I/O 事件的信号处理函数。发起 I/O 请求后进程立即返回继续执行其他任务。内核等待数据就绪后向进程发送指定信号如SIGIO。进程捕获信号后暂停当前任务调用 I/O 接口阻塞等待数据复制完成。执行流程进程注册信号处理函数 → 发起 I/O 请求并立即返回 → 内核等待数据就绪 → 内核发送SIGIO信号 → 进程捕获信号调用recvfrom完成数据拷贝。特点与适用场景优点等待数据阶段无需阻塞和轮询CPU 利用率优于非阻塞 I/O。缺点信号处理逻辑复杂信号队列存在溢出风险高并发场景下大量信号会导致程序稳定性下降。适用场景生产环境使用极少仅适用于连接数少、对实时性有一定要求的特殊场景。1.5 异步 I/O 模型Asynchronous I/O工作原理异步 I/O 是真正意义上的异步操作。进程发起异步 I/O 调用如aio_read并指定回调函数后系统调用会立即返回。内核独立完成等待数据就绪 数据复制的全部流程完成后通过信号或回调函数通知进程。进程全程无需阻塞可直接使用已经拷贝到用户空间的数据。执行流程进程调用aio_read并注册回调 → 调用立即返回进程执行其他任务 → 内核完成数据等待与拷贝 → 内核通知进程 → 进程通过回调处理数据。特点与适用场景优点I/O 全程无阻塞CPU 利用率达到最高是性能最优的 I/O 模型。缺点编程逻辑复杂不同操作系统的 API 和支持度差异极大调试和维护成本高。适用场景对性能、响应速度要求极致的场景如高性能数据库、高速网络存储系统。1.6 五种 I/O 模型核心对比模型等待数据阶段数据复制阶段实现难度CPU 利用率并发能力阻塞 I/O阻塞阻塞低低差非阻塞 I/O非阻塞轮询阻塞较低中较差I/O 多路复用阻塞监听多路阻塞中等较高优秀信号驱动 I/O非阻塞信号通知阻塞较高较高一般异步 I/O非阻塞非阻塞高最高优秀补充说明日常高并发服务开发中I/O 多路复用模型是最常用的选择。它在性能、开发成本、跨平台兼容性之间取得了极佳的平衡。异步 I/O 虽然性能顶尖但受限于平台兼容性和开发复杂度仅在极致性能场景下使用。2. 五种IO模型2.1 阻塞IO阻塞IO在内核将数据准备好之前系统调用会一直等待。所有的套接字默认都是阻塞方式。阻塞IO是最常见的IO模型。2.2 非阻塞IO非阻塞IO如果内核还未将数据准备好系统调用仍然会直接返回并且返回EWOULDBLOCK错误码。非阻塞IO往往需要程序员循环的方式反复尝试读写文件描述符这个过程称为轮询。这对CPU来说是较大的浪费一般只有特定场景下才使用。2.3 IO多路转接• IO多路转接虽然从流程图上看起来和阻塞IO类似。实际上最核心在于IO多路转接能够同时等待多个文件描述符的就绪状态。2.4 信号驱动IO信号驱动IO内核将数据准备好的时候使用SIGIO信号通知应用程序进行IO操作。2.5 异步IO异步IO由内核在数据拷贝完成时通知应用程序(而信号驱动是告诉应用程序何时可以开始拷贝数据)。小结任何IO过程中都包含两个步骤。第一是等待第二是拷贝。而且在实际的应用场景中等待消耗的时间往往都远远高于拷贝的时间。让IO更高效最核心的办法就是让等待的时间尽量少。3. 高级IO重要概念在这里, 我们要强调几个概念3.1 同步通信 vs 异步通信(synchronous communication/ asynchronous3.1.1 概念同步通信与异步通信的核心关注对象是进程 / 线程间的调用发起与结果交付的通信机制。这组概念常和网络 I/O、进程间通信场景绑定需要和「进程 / 线程同步」的概念严格区分二者不可混为一谈。同步通信同步通信的核心特征调用方发起调用请求后在被调用方完成操作、返回最终结果之前当前调用会一直阻塞不会提前返回。调用方会主动等待直至拿到调用的返回结果才能继续执行后续逻辑。结合我们之前学习的五种 I/O 模型阻塞 I/O、非阻塞 I/O、I/O 多路复用、信号驱动 I/O 都属于同步 I/O其底层逻辑也完全契合同步通信的定义 —— 数据从内核缓冲区复制到用户缓冲区的阶段调用进程始终需要等待操作完成。异步通信异步通信与同步通信的行为完全相反调用方发起调用请求后该调用会立即返回调用返回时不会携带本次操作的最终结果。调用方无需阻塞等待可以继续执行其他任务。被调用方在后台独立完成全部操作后会通过状态反馈、系统通知、回调函数等方式告知调用方操作已完成并交付最终的处理结果。对应到 I/O 模型中只有异步 I/OAIO完全符合异步通信的定义。3.1.2 易混淆概念区分两种「同步」的本质差异在多进程、多线程的学习中我们会接触到「进程 / 线程同步与互斥」的概念。这里的进程 / 线程同步和上文的同步通信是完全独立、语境不同的技术概念学习时必须做好区分。进程 / 线程同步进程 / 线程同步描述的是多个并发执行的进程或线程之间为协同完成同一任务而产生的直接制约关系。多个执行单元为了安全访问临界资源、保证业务执行的先后次序需要在特定的逻辑节点进行等待、唤醒、消息传递避免出现竞态条件、数据错乱等问题。例如线程 A 必须等待线程 B 完成数据写入后才能执行读取操作这种协作约束的机制就是进程 / 线程同步。其核心目的是保障并发程序执行的安全性与有序性。语境判断方法后续阅读技术资料、编写代码时看到「同步」一词先明确其所处的技术语境即可快速区分若语境围绕函数调用、消息传递、I/O 操作展开讨论的是同步通信核心关注调用是否阻塞、结果是否立即返回。若语境围绕多进程 / 多线程并发、临界资源访问、执行次序协调展开讨论的是进程 / 线程同步核心关注多个执行单元的协作约束。3.2 阻塞 vs 非阻塞阻塞和非阻塞的核心关注点是进程或线程在发起系统调用、函数调用后等待操作结果的过程中自身所处的执行状态。它和上一节的同步 / 异步通信属于两个完全独立的评判维度二者可以相互组合不能直接划等号。阻塞调用阻塞调用的核心特征线程发起调用后在对应操作彻底完成、最终结果返回之前当前线程会被操作系统内核挂起进入阻塞休眠状态。该线程会暂时放弃 CPU 的使用权不会执行任何后续指令直到内核完成操作并返回结果线程才会被唤醒继续执行后续代码。结合我们学习的 I/O 模型阻塞 I/O是最典型的阻塞调用场景线程在等待数据就绪、内核拷贝数据的整个流程中都处于阻塞状态。非阻塞调用非阻塞调用的核心特征线程发起调用后即便当前操作无法立刻完成、不能获取最终结果该调用也不会阻塞当前线程而是会立即返回。调用返回时内核通常会返回特定的状态码或错误码告知调用方当前操作暂未就绪。线程可以在调用返回后继续处理其他任务后续再通过轮询、事件监听等方式重新发起调用以获取最终结果。例如非阻塞 I/O 模型数据未就绪时recvfrom调用会立即返回错误提示线程无需挂起等待。3.3 阻塞 / 非阻塞 与 同步 / 异步的核心区分这是学习 I/O 模型时的高频易错点补充该部分内容可以彻底厘清概念关注维度不同阻塞 / 非阻塞聚焦调用方线程的运行状态判断线程是被挂起休眠还是持续运行。同步 / 异步聚焦调用结果的交付方式判断结果是调用时同步获取还是通过后续通知、回调异步获取。常见的组合形式两个概念可以自由组合形成不同的调用模式也是 I/O 模型的分类依据同步阻塞经典阻塞 I/O线程阻塞等待结果同步返回。同步非阻塞非阻塞 I/O线程不阻塞但需要主动轮询调用获取结果仍属于同步通信。异步非阻塞异步 I/O线程不阻塞结果由内核通过信号、回调异步通知是高性能的常用组合。异步阻塞实际工程开发中几乎没有实用价值极少使用。3.4 其他高级IO非阻塞IO纪录锁系统V流机制I/O多路转接也叫I/O多路复用,readv和writev函数以及存储映射IOmmap这些统称为高级IO.我们此处重点讨论的是I/O多路转接4. 非阻塞IO4.1 fcntl在类 Unix/Linux 系统中所有通过open、socket等系统调用打开的文件描述符fd如网络 Socket、普通文件、管道、设备等默认的 I/O 工作模式均为阻塞 I/O。若要将其改为非阻塞模式最常用的工具就是fcntl函数 —— 该函数是对文件描述符进行属性控制与状态修改的核心系统调用#includeunistd.h#includefcntl.h// 函数返回值成功返回对应操作的结果依cmd而定失败返回-1并设置errnointfcntl(intfd,intcmd,.../* arg */);// 函数参数说明// fd需要进行属性控制的文件描述符如 socket 返回的 fd、open 返回的文件 fd// cmd操作命令决定 fcntl 要执行的具体功能如获取状态、设置非阻塞、复制 fd 等// ...可变参数是否需要传参、传什么类型的参数完全由cmd的取值决定 —— 部分 cmd 无需额外参数部分 // cmd 需要传入int类型的 arg 参数。fcntl 的功能通过cmd参数区分核心分为 5 类均为文件描述符的属性 / 状态操作术语与系统标准保持一致复制一个现有的文件描述符cmdF_DUPFD/F_DUPFD_CLOEXEC生成一个新的文件描述符与原 fd 指向同一文件 / 设备支持设置新 fd 的最小取值获取 / 设置文件描述符标志cmdF_GETFD/F_SETFD操作 fd 本身的标志如FD_CLOEXEC进程执行exec时自动关闭该 fd获取 / 设置文件状态标志cmdF_GETFL/F_SETFL操作文件的 I/O 模式标志设置非阻塞的核心操作如O_NONBLOCK、O_APPEND等获取 / 设置异步 I/O 的属主cmdF_GETOWN/F_SETOWN指定接收异步 I/O 信号如SIGIO的进程 ID 或线程 ID为信号驱动 I/O 做准备获取 / 设置文件记录锁cmdF_GETLK/F_SETLK/F_SETLKW对文件进行读 / 写锁控制实现多进程对文件的同步访问避免竞态。我们此处只是用第三种功能, 获取/设置文件状态标记, 就可以将一个文件描述符设置为非阻塞.4.1.1 核心实操将文件描述符设为非阻塞模式前文讲解了阻塞 / 非阻塞的概念这里补充最常用的实战用法—— 通过fcntl的F_GETFL和F_SETFL命令将默认的阻塞 fd 改为非阻塞这是网络编程、I/O 操作中最基础的步骤步骤分为 3 步调用fcntl(fd, F_GETFL)获取 fd 当前的文件状态标志将获取到的标志与非阻塞标志O_NONBLOCK进行按位或操作保留原有标志仅添加非阻塞属性调用fcntl(fd, F_SETFL, 新标志)将修改后的标志写回 fd完成非阻塞模式设置。封装成工具函数可直接复用#includeunistd.h#includefcntl.h#includeerrno.h// 将文件描述符设置为非阻塞模式// 返回值成功返回0失败返回-1intset_nonblock(intfd){// 1. 获取当前的文件状态标志intold_flfcntl(fd,F_GETFL);if(old_fl-1){return-1;// 获取失败直接返回}// 2. 按位或O_NONBLOCK添加非阻塞属性保留原有所有标志intnew_flold_fl|O_NONBLOCK;// 3. 将新标志写回文件描述符if(fcntl(fd,F_SETFL,new_fl)-1){return-1;}return0;}关键注意事项不可直接设置O_NONBLOCK必须先获取原有标志再按位或否则会覆盖 fd 原有的状态如O_APPEND追加写、O_RDWR读写模式等F_SETFL可修改的标志有限仅能修改O_NONBLOCK非阻塞、O_APPEND追加写、O_ASYNC异步 I/O等少数标志文件的读写模式如O_RDONLY/O_WRONLY无法通过fcntl修改错误处理fcntl 操作失败会返回 - 1 并设置errno可通过perror(fcntl)打印具体错误原因如 fd 无效、权限不足。…过云雨-CSDN博客