Linux 学习笔记(五十五):五种 IO 模型与非阻塞 IO 实战 核心结论TL;DR阻塞 vs 非阻塞阻塞是死等结果非阻塞是立刻返回并轮询。五种 IO 模型阻塞 IO、非阻塞 IO、信号驱动 IO、IO 多路复用epoll、异步 IO。fcntl函数用于修改文件描述符的属性通过设置O_NONBLOCK标志可以将阻塞 IO 变为非阻塞。非阻塞 IO 的陷阱如果在循环中频繁调用非阻塞read会导致 CPU 占用率飙升忙等待通常需要配合epoll等机制使用。一、 为什么说 IO 是后端开发的瓶颈任何 IO 操作都分为两个阶段等待数据就绪和数据拷贝。在实际应用中“等待数据”消耗的时间远大于“拷贝数据”。要想提高 IO 效率核心就是减少程序在“等待”时对 CPU 的占用。二、 五种 IO 模型的“钓鱼”比喻阻塞 IO死盯浮漂鱼不上钩不动弹默认的 read/write。非阻塞 IO扔下鱼竿去干别的过一会看一眼轮询极度耗费体力CPU。信号驱动 IO浮漂上挂铃铛铃响去收杆。IO 多路复用同时看多根鱼竿select/poll/epoll哪一个有动静收哪个高并发服务器的标配。异步 IO雇人钓鱼钓好了直接送嘴边内核把数据拷贝完才通知。三、 实战把阻塞 IO 变成非阻塞void SetNoBlock(int fd) { int fl fcntl(fd, F_GETFL); // 获取状态 fcntl(fd, F_SETFL, fl | O_NONBLOCK); // 设置非阻塞 }利用fcntl修改文件描述符属性后read如果读不到数据就不会死等而是立刻返回-1并设置errno为EAGAIN。我们通过在死循环里判断这个错误码就实现了非阻塞的读取。四、 踩坑记录非阻塞 IO 的忙等待如果在循环里去掉了usleep程序会疯狂地请求 CPU 去查有没有数据导致 CPU 占用率瞬间跑满。为了解决这个问题同时又能在非阻塞的情况下监听多个 socketIO 多路复用epoll应运而生它既能一次监听上万个连接又能在没有数据时让出 CPU。自我实战操作cd ~/linux_study/network_test实战 1体验默认的阻塞 IObashvim io_block.c(输入以下代码)#include stdio.h #include unistd.h int main() { char buf[1024]; printf(请输入内容我会在这里一直死等\n); // read 默认是阻塞的如果没有输入主线程会一直挂起 read(0, buf, sizeof(buf)); printf(你输入了: %s\n, buf); return 0; }(编译并运行gcc io_block.c -o io_block ./io_block)现象程序运行后如果你不敲键盘它就一直卡在那里。这就叫阻塞。实战 2用fcntl将标准输入改为非阻塞vim io_nonblock.c(输入以下代码)#include stdio.h #include unistd.h #include fcntl.h #include errno.h #include string.h // 核心函数将文件描述符设置为非阻塞 void SetNoBlock(int fd) { int fl fcntl(fd, F_GETFL); // 1. 获取当前文件描述符的属性 if (fl 0) { perror(fcntl); return; } fcntl(fd, F_SETFL, fl | O_NONBLOCK); // 2. 在原有基础上加上非阻塞属性 } int main() { SetNoBlock(0); // 0 代表标准输入键盘 char buf[1024]; while (1) { memset(buf, 0, sizeof(buf)); ssize_t n read(0, buf, sizeof(buf) - 1); if (n 0) { printf(你输入了: %s, buf); } else if (n 0) { // 如果没有数据read 会返回 -1并把 errno 设置为 EAGAIN 或 EWOULDBLOCK if (errno EAGAIN || errno EWOULDBLOCK) { // 这里就是经典的“轮询”过程 // 为了不在终端疯狂刷屏我们睡 1 秒 usleep(1000000); } else { perror(read); break; } } } return 0; }(编译并运行gcc io_nonblock.c -o io_nonblock ./io_nonblock)现象程序运行后终端不再卡死而是处于一个“活”的状态。当你敲下键盘并回车它能立刻捕捉到并打印出来。这就是非阻塞 IO。⚠️ 实战排坑别在非阻塞 IO 里写死循环如果你把上面代码里的usleep删掉程序会变成一个死循环CPU 占用率瞬间飙升到 100%这就是经典的“忙等待”。这就引出了我们下一课的重点IO 多路复用epoll它可以在不浪费 CPU 的情况下等待多个文件描述符。