本文最后更新于11 天前,其中的信息可能已经过时,如有错误请发送邮件到2156936367@qq.com
Linux多路复用机制
select
核心特点:
- 使用 bitmap(位图)
- 每次调用需要从用户态拷贝 fd 集合到内核态
- 需要遍历全部 fd
问题:
- fd 数量限制(通常 1024),因为bitmap就是1024位
- 修改原 fd 集合(需要重置)
- 每次都要 全量扫描,复杂度仍是 O(n)
- 每次调用需要:用户态 → 内核态拷贝 fd 集合,cpu上下文切换消耗资源
poll
与 select 原理一样:
- 每次调用需要从用户态拷贝 fd 集合到内核态
- 需要遍历全部 fd
不同的是:
- 用 数组(pollfd) 替代 bitmap
- 没有 fd 数量限制
问题相对于select肯定也是少了一点:
- 每次都要 全量扫描,复杂度仍是 O(n)
- 每次调用需要:用户态 → 内核态拷贝 fd 集合,cpu上下文切换消耗资源
epoll
核心:事件驱动 + 回调机制
内核数据结构:
- 一颗红黑树——存所有的 fd
- 一个就绪链表——存有数据(事件)的 fd
工作流程:
- 首先就是 epoll_create 在内核创建 epoll 实例(eventpoll)
- 然后 epoll_ctl 将 fd 注册到红黑树,并绑定回调函数
- 当数据到达时,网卡中断,内核协议栈,socket buffer写入,内核调用 fd 对应的回调机制(将 fd 加入到 ready list 就序链表)
- 最后 epoll_wait 用户态调用,内核直接返回 ready list ,不需要像 select 和 poll 那样 遍历全部 fd
三者的本质区别总结
- select / poll:你每次问内核:哪些 fd 好了
- 每次调用都需要从用户态传递 fd 集合到内核
- 内核需要遍历所有 fd 判断是否就绪
- 时间复杂度 O(n)
- epoll:内核帮你盯着,有事件直接通知你
- 通过 epoll_ctl 将 fd 注册到内核(红黑树)
- 内核通过回调机制,在 fd 就绪时加入 ready list
- epoll_wait 只返回 ready list
- 避免全量遍历,性能接近 O(1)
补充
- 为什么 select / poll 慢?主要不是 用户态 拷贝到 内核态的原因,而是无效扫描太多了
- epoll 为什么快?利用中断 + 回调 ,直接返回 ready list ,避免了无效遍历
- epoll 两种模式
- LT(Level Trigger,默认):只要缓冲区有数据就一直通知,不处理就一直通知
- ET(Edge Trigger):只在状态变化时通知一次,只提醒一次,自己处理。要求必须非阻塞 IO ,要读就一次读干净
- 惊群效应多个线程/进程同时监听在同一个epoll,可能一个事件唤醒多个线程,造成资源浪费
Nginx 为什么用 epoll?
前言
讲完epoll,再结合自己最近学的Nginx,可以看看nginx 为何要用到epoll,后面其实还有 reids 数据库 也要用到 epoll,现在暂时不讲。我们应该知道nginx不是多线程,而是多进程,单线程,而且nginx通常适用于高并发场景,单线程怎么处理高并发的,接下来就来读懂这后边的秘密吧
Nginx 核心架构
Nginx 不是多线程模型,而是:多进程 + 单线程事件循环(Reactor 模型)
每个 worker 进程:
- 只有 一个线程
- 一个 事件循环(event loop)
- 同时处理成千上万连接(10w+)
想想如果用select/poll 会怎么样?那简直就是灾难
假设10 万个连接,只有 100 个有数据
select / poll 每次:
- 用户态传 10 万 fd
- 内核遍历 10 万 fd
- 找出 100 个活跃 fd
结果:99.9% 都是无效扫描,CPU 被白白浪费,nginx单线程直接被拖死了
而epoll 的事件驱动机制和回调机制只处理了10万个连接中的100个有数据的连接
单线程 + epoll 的优势
很多人的误区:“多线程不是更快吗?”
在 Nginx 场景下,恰恰相反
1. 避免线程切换
多线程问题:
- 上下文切换(CPU 开销)
- 锁竞争
epoll + 单线程:
- 无锁
- 无切换
2. IO 不阻塞
Nginx 使用:非阻塞 IO + epoll
流程:
- accept 新连接
- 注册 epoll
- 有数据才处理
- 没数据立即返回
整个 worker 永远不会被一个连接卡住
Nginx + epoll 完整工作流程
- 首先 worker 创建 epoll 实例
- 将 fd 加入红黑树
- 当数据到达时,网卡中断,内核协议栈,socket buffer写入,内核调用 fd 对应的回调机制(将 fd 加入到 ready list 就序链表)
- 最后 Nginx 调用epoll_wait ,内核直接返回 ready list ,不需要像 select 和 poll 那样 遍历全部 fd
nginx 使用 epoll 总结
- Nginx 采用单线程 Reactor 模型,需要同时处理大量连接
- select / poll 每次都要遍历所有 fd,复杂度 O(n),在高并发场景下性能差
- epoll 通过回调机制将就绪 fd 放入 ready list,epoll_wait 只返回活跃连接
- 避免全量扫描,性能接近 O(1)
- 配合非阻塞 IO,实现单线程处理高并发连接
- Nginx 常用的是 ET(边缘触发),只通知一遍



