当前位置: 首页 > news >正文

Linux笔记---select、poll、epoll总结对比

1. select:最早的 IO 多路复用方案

select 是 POSIX 标准定义的 IO 多路复用接口,兼容性强(几乎所有 Unix-like 系统支持,包括windows都支持),但设计存在明显局限性。

1.1 核心原理

FD 集合管理:使用固定大小的 fd_set 结构体(本质是位图)存储待监控的 FD,支持三种集合:

  • readfds:监控 “可读” 事件的 FD 集合;
  • writefds:监控 “可写” 事件的 FD 集合;
  • exceptfds:监控 “异常” 事件的 FD 集合。

调用流程:

  1. 用户需手动初始化 fd_set,将待监控的 FD 加入对应集合;
  2. 调用 select(int nfds, fd_set *readfds, ...),阻塞等待 IO 事件;
  3. 内核遍历所有 FD,检查是否有就绪事件,将就绪 FD 保留在 fd_set 中,返回就绪 FD 数量;
  4. 用户需遍历 fd_set,通过 FD_ISSET(fd, &set) 判断 FD 是否就绪。

1.2 关键局限性

  • FD 数量上限:fd_set 大小固定(由宏 FD_SETSIZE 定义,默认 1024),且编译后无法动态调整(需修改内核参数重新编译,成本高);
  • 数据拷贝开销:每次调用 select 时,需将 fd_set 从用户空间拷贝到内核空间,FD 数量越多,拷贝开销越大;
  • 遍历效率低:内核需遍历所有监控的 FD 才能确定就绪状态,用户空间也需遍历 fd_set 排查就绪 FD,时间复杂度 O (n),FD 数量多时效率急剧下降;
  • 集合需重复初始化:select 返回后,fd_set 中仅保留就绪 FD,下次调用前需重新将所有待监控 FD 加入集合,操作繁琐。

2. poll:对 select 的简单改进

poll 同样是 POSIX 标准接口,旨在解决 select 的部分局限性,但核心效率问题未根治。

2.1 核心原理

FD 数组管理:使用动态数组 struct pollfd *fds 存储待监控 FD,每个 pollfd 结构体包含:

struct pollfd {int fd;         // 待监控的FDshort events;   // 用户关注的事件(如POLLIN:可读,POLLOUT:可写)short revents;  // 内核返回的就绪事件(避免覆盖events)
};

调用流程:

  1. 用户初始化 pollfd 数组,设置每个 FD 的关注事件;
  2. 调用 poll(struct pollfd *fds, nfds_t nfds, int timeout),阻塞等待;
  3. 内核遍历数组,将就绪事件写入 revents,返回就绪 FD 数量;
  4. 用户遍历数组,通过 revents 判断 FD 是否就绪。

2.2 改进与残留问题

  • 突破 FD 数量上限:pollfd 是动态数组,FD 数量仅受系统内存限制(理论上无上限);
  • 无需重复初始化:events 存储关注事件,revents 存储就绪事件,数组可重复使用,无需每次重新设置;
  • 核心效率问题未解决
    1. 每次调用仍需将整个 pollfd 数组从用户空间拷贝到内核空间(拷贝开销随 FD 数量增长);
    2. 内核和用户空间仍需遍历所有 FD 排查就绪状态(时间复杂度 O (n)),高并发场景下效率依然低下。

3. epoll:Linux 专属的高效方案

epoll 是 Linux 2.6 内核后引入的 IO 多路复用机制,针对高并发场景设计,彻底解决了 select/poll 的效率瓶颈,是 Nginx、Redis 等高性能软件的核心 IO 模型。

3.1 核心原理

内核维护 “注册 - 就绪” 分离结构:

  • 红黑树:存储用户注册的所有待监控 FD(支持高效的增 / 删 / 查操作,时间复杂度 O (log n));
  • 就绪链表:仅存储就绪的 FD(内核触发 IO 事件后,直接将 FD 移入链表,无需遍历所有 FD)。

三个核心函数:

  • int epoll_create:创建 epoll 实例,返回 epoll 文件描述符(epfd),内核初始化红黑树和就绪链表;
  • int epoll_ctl:管理 FD 的注册 / 修改 / 删除:
    • op:操作类型(EPOLL_CTL_ADD:注册,EPOLL_CTL_MOD:修改事件,EPOLL_CTL_DEL:删除);
    • event:存储 FD 的关注事件(如 EPOLLIN、EPOLLOUT)和用户数据(如 FD 本身);
  • int epoll_wait:等待就绪事件:
    • 内核直接从 “就绪链表” 复制就绪 FD 的事件到 events 数组;
    • 返回就绪 FD 的数量,用户无需遍历所有注册 FD,仅需处理 events 数组即可。

3.2 关键优势:高效与灵活

  • 无 FD 数量上限:红黑树存储 FD,数量仅受系统内存限制(支持百万级 FD 监控);
  • 零拷贝 + 按需拷贝: FD 的注册 / 修改 / 删除仅通过 epoll_ctl 操作一次,内核红黑树直接存储 FD,无需每次调用拷贝所有 FD(仅 epoll_wait 时拷贝就绪 FD 的事件,开销极小);
  • 就绪事件主动通知:内核通过 “就绪链表” 直接提供就绪 FD,无需遍历所有注册 FD(时间复杂度 O (1),仅处理就绪 FD);
  • 支持两种触发模式(select/poll 仅支持水平触发)
    • 水平触发(LT,Level Trigger):默认模式。只要 FD 有未处理的就绪数据,每次调用 epoll_wait 都会重复通知(容错性高,编程简单,适合大多数场景);
    • 边缘触发(ET,Edge Trigger):仅在 FD 状态从 “未就绪” 变为 “就绪” 时通知一次(需一次性读取 / 写入所有数据,且必须搭配非阻塞 IO,效率更高,适合高并发场景)。

4. 对比

对比维度selectpollepoll(Linux)
FD 数量限制固定上限(FD_SETSIZE 默认 1024)无上限(仅受系统内存限制)无上限(仅受系统内存限制)
用户空间→内核空间拷贝每次调用拷贝整个 fd_set每次调用拷贝整个 pollfd 数组仅 epoll_ctl 时拷贝(注册 / 修改 / 删除时一次)
内核就绪检测方式遍历所有监控 FD(O (n))遍历所有监控 FD(O (n))就绪链表直接返回(O (1),仅处理就绪 FD)
用户空间就绪排查遍历 fd_set(O (n))遍历 pollfd 数组(O (n))遍历 epoll_wait 返回的就绪数组(O (k),k 为就绪 FD 数)
触发模式仅水平触发(LT)仅水平触发(LT)支持 LT(默认)和 ET(高效)
时间复杂度O (n)(n 为监控 FD 总数)O (n)(n 为监控 FD 总数)O (1)(仅与就绪 FD 数 k 相关)
兼容性所有 Unix-like 系统(POSIX 标准)所有 Unix-like 系统(POSIX 标准)仅 Linux 2.6 + 内核
编程复杂度较高(需重复初始化 fd_set)较低(pollfd 数组可复用)中等(需理解 ET 模式与非阻塞 IO 配合)

5. 适用场景总结

  • select:小规模、低并发场景 适用场景
    • 监控 FD 数量少(≤1024)、并发连接少的简单服务(如小型工具、测试程序);
    • 优势:兼容性极强,无需考虑系统差异;
    • 劣势:FD 上限和效率问题无法解决,不适合高并发。
  • poll:FD 数量不固定但并发不高的场景
    • 适用场景:监控 FD 数量超过 1024 但并发连接较少的服务(如中小规模的内部服务);
    • 优势:突破 FD 数量上限,编程比 select 简单;
    • 劣势:高并发下遍历和拷贝开销依然明显,效率低于 epoll。
  • epoll:高并发、大规模连接场景
    • 适用场景:百万级并发连接、高 IO 压力的服务(如 Web 服务器 Nginx、缓存 Redis、消息队列 Kafka);
    • 优势:效率不随 FD 数量增长而下降,支持 ET 模式进一步提升性能;
    • 劣势:仅支持 Linux 系统,跨平台场景需适配其他方案(如 BSD 的 kqueue、Solaris 的 /dev/poll)。
  • 效率排序epoll(ET 模式) > epoll(LT 模式) > poll > select
  • 高并发首选:Linux 环境下,epoll 是唯一能支撑百万级连接的高效方案。
http://www.dtcms.com/a/395210.html

相关文章:

  • MySQL查询详细介绍
  • 面试题二:业务篇
  • Rust进阶-part8-迭代器
  • halcon3d gen_image_to_world_plan3_map与project_3d_point
  • Ellisys工具
  • Qwen3-7B-Instruct Windows LMStudio 部署
  • 【代码】关于C#支持文件和文本框的简单日志实现
  • atcoder经典好题
  • 【Linux】Linux文件系统详解:从磁盘到文件的奥秘
  • 【Android Keystore】Android 密钥库系统使用指南
  • RBAC权限模型实战图解:绘制企业权限矩阵,告别混乱授权
  • 【ROS2】通讯协议接口 Interface
  • Spring —— 事务控制
  • 基于vue开发的背单词网站
  • javascript 角色跟踪实践
  • 第九周作业
  • 【ThinkPHP项目添加新页面完整解决方案】
  • Thinkphp框架相关漏洞扫描器(一)
  • 【网络通讯】Qt中使用Modbus Tcp协议(附Demo)
  • 在 macOS 上使用 Windows 快捷键指南
  • pd26 虚拟机 (Mac中文)
  • 本周的股指
  • (论文速读)生成式摄影:让AI理解相机的物理世界
  • ELK 企业级日志分析系统
  • 项目日记 -日志系统 -功能完善
  • install_docker.sh
  • opencv的DNN模块里
  • FPGA学习笔记——图像处理之对比度调节(线性调节)
  • SkyWalking 核心概念与智能探针工作原理深度揭秘(上)
  • leetcode hot100 简单难度 day02-刷题