linux 上不推荐 select 用于高并发,因其 fd_set 固定大小(通常 1024),每次调用需全量拷贝和线性扫描,连接数超千时性能断崖下降;poll 虽无数量限制但仍为 o(n) 时间复杂度;epoll 才是高并发首选,但需注意 epollet 使用规范、fd 关闭顺序及多线程安全。

select 为什么在 Linux 上不推荐用于高并发
因为 select 的 fd_set 是固定大小(通常 1024),每次调用都要把整个位图从用户态拷贝到内核态,且内核需线性扫描所有 fd 判断就绪状态。连接数一过千,性能断崖式下降。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 仅在跨平台兼容性要求极高、且并发量稳定低于 100 的场景用
select - Linux 下避免用
FD_SETSIZE手动扩大上限——它只是改了栈上数组大小,内核仍按默认值检查,容易触发EBADF或静默失败 -
select返回后必须重置fd_set,否则下次调用会漏掉新加入的 fd;常见错误是只清空一次、复用旧集合
poll 比 select 好在哪,但仍有硬伤
poll 用动态数组替代位图,理论上无 fd 数量硬限制,也不再需要每次重置结构体。但它仍要遍历全部注册的 fd,时间复杂度 O(n),且内核与用户空间仍需拷贝整个 struct pollfd 数组。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 用
pollfd数组时,记得为每个元素显式设events = POLLIN | POLLOUT,否则默认为 0,永远等不到事件 - 处理
POLLHUP和POLLERR必须和POLLIN分开判断——它们不保证有数据可读,直接recv()可能返回 0 或 -1 +EAGAIN - 不要在循环里反复
poll()同一个 fd 而不read()/write(),会陷入“就绪→无数据→再就绪”死循环
epoll_create1(0) 和 epoll_ctl 的关键参数陷阱
Linux 下真正实用的非阻塞 I/O 底层是 epoll,但封装时几个参数极易出错:epoll_create1(0) 比 epoll_create(1024) 更安全(后者参数被忽略,仅作兼容);epoll_ctl 的 op 参数若传错 EPOLL_CTL_MOD 却没先 ADD 过,会返回 ENOENT。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 注册 fd 时,
event.events至少包含EPOLLIN,如果要边写边读,加上EPOLLET启用边缘触发,否则默认水平触发可能反复唤醒 - 边缘触发下,必须循环
recv()直到返回-1且errno == EAGAIN,否则会丢数据 -
epoll_wait()的 timeout 设为 -1 表示永久阻塞,0 表示纯轮询(极耗 CPU),生产环境慎用 0
封装成类时最容易漏掉的资源清理点
很多 C++ 封装把 epoll_fd 放在 RAII 类里,却忘了 fd 关闭顺序:必须先 close() 所有被监听的 socket,再 close(epoll_fd)。否则内核可能残留引用,导致 epoll_wait() 返回已关闭 fd 的就绪事件,后续 read() 触发 EBADF。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 析构函数中,先遍历保存的 socket fd 列表并
close(),再关epoll_fd;别依赖容器自动析构——socket fd 是裸 int,不会自动 close - 用
epoll_ctl(EPOLL_CTL_DEL)删除 fd 时,即使失败(如 fd 已关闭),也应继续执行后续逻辑,不能 throw 或 abort,否则 cleanup 流程中断 - 多线程环境下,不要在 worker 线程里直接
epoll_ctl修改监听列表——epoll本身线程安全,但你的 fd 管理逻辑未必,加锁或统一由 event loop 线程操作
边缘触发、fd 生命周期、事件漏判——这三个地方出问题,比语法错误更难 debug,因为现象往往是偶发连接卡住或数据截断,而不是崩溃或报错。










