poll用链表遍历o(n),epoll用红黑树+就绪队列实现o(log n)注册和o(1)就绪获取;poll跨平台兼容但无et模式,适合低连接高活跃场景;epoll需注意epoll_ctl操作规范与close同步。

poll 用的是链表,epoll 用的是红黑树 + 就绪队列
poll 在内核中维护一个 struct pollfd 数组(实际底层是链表结构),每次调用 poll() 都要把整个数组从用户空间拷贝进内核,然后线性遍历每个 fd 查询状态。它不预分配固定大小,所以没 1024 限制,但遍历开销仍是 O(n)。
epoll 的核心是两个分离结构:
– “总花名册”:用红黑树存所有注册的 fd,支持快速插入、删除、查找(O(log n));
– “待命小喇叭”:用就绪队列(类似 FIFO)只存已触发事件的 fd,epoll_wait() 直接取队头,无须遍历(平均 O(1))。
这个设计差异直接决定了:poll 适合中等连接数(几千)、连接活跃度高的场景;epoll 适合高并发(几万+)、大量空闲连接的服务器。
什么时候该用 poll 而不是 epoll
你写的是跨平台 C++ 网络库,目标系统包括 macOS 或旧版 FreeBSD —— 这些系统不支持 epoll,只能用 poll 或 kqueue。
或者你的服务连接数稳定在 2000 以内,且每个连接都高频读写(比如实时音视频中继),这时 poll 的简单性和低回调开销反而比 epoll 的红黑树管理更轻量。
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
常见误判点:
– 不要因为“epoll 更快”就在开发机上硬切——本地测试几百连接时,poll 和 epoll 性能差不到 1%;
– poll 没有边缘触发(ET)模式,全是水平触发(LT),如果你依赖 ET 做一次性收包,poll 根本不满足需求。
epoll_ctl 注册时的 op 参数影响红黑树行为
epoll_ctl() 的第三个参数 op 决定了红黑树节点如何被操作:
-
EPOLL_CTL_ADD:插入新节点,若fd已存在会返回EEXIST; -
EPOLL_CTL_MOD:更新已有节点的events字段(比如从只读改成读写),不涉及树结构调整; -
EPOLL_CTL_DEL:从红黑树中删除节点,必须确保fd当前确实在 epoll 实例中,否则返回ENOENT。
错误做法:
– 多次 EPOLL_CTL_ADD 同一个 fd → 红黑树不会覆盖,而是报错;
– 忘记 close() 前调用 EPOLL_CTL_DEL → 节点残留,泄漏内核资源;
– 在多线程中并发调用 epoll_ctl 且未加锁 → 红黑树可能短暂不一致(虽然内核做了部分保护,但用户态逻辑仍需同步)。
epoll 的 mmap 共享内存只用于 event 数组传递
很多人以为 epoll 整个机制靠 mmap 加速,其实不是:
– 内核只对 epoll_wait() 返回的就绪事件数组(struct epoll_event *)做了页映射;
– epoll_ctl() 的注册动作仍走常规系统调用路径,不经过 mmap;
– 所以只有当你一次等待大量就绪事件(比如 >1000 个)时,mmap 才真正减少复制开销。
这意味着:
– 小规模就绪(如每次 1~5 个事件),mmap 优势几乎不可测;
– 如果你把 epoll_wait() 的 maxevents 设成 1,等于主动放弃 mmap 优化;
– 别在栈上分配太大的 epoll_event 数组(比如 struct epoll_event events[10240]),容易栈溢出,应 malloc 或用 std::vector 管理。
close() 后是否立刻从 epoll 移除,不是“可选”,而是避免后续 epoll_wait 返回已失效 fd 的必要操作。C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










