一文带你学清楚Linux IO多路复用机制和与Nginx的结合
本文最后更新于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

工作流程:

  1. 首先就是 epoll_create 在内核创建 epoll 实例(eventpoll)
  2. 然后 epoll_ctl 将 fd 注册到红黑树,并绑定回调函数
  3. 当数据到达时,网卡中断,内核协议栈,socket buffer写入,内核调用 fd 对应的回调机制(将 fd 加入到 ready list 就序链表)
  4. 最后 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)

补充

  1. 为什么 select / poll 慢?主要不是 用户态 拷贝到 内核态的原因,而是无效扫描太多了
  2. epoll 为什么快?利用中断 + 回调 ,直接返回 ready list ,避免了无效遍历
  3. epoll 两种模式
  • LT(Level Trigger,默认):只要缓冲区有数据就一直通知,不处理就一直通知
  • ET(Edge Trigger):只在状态变化时通知一次,只提醒一次,自己处理。要求必须非阻塞 IO ,要读就一次读干净
  1. 惊群效应多个线程/进程同时监听在同一个epoll,可能一个事件唤醒多个线程,造成资源浪费

Nginx 为什么用 epoll?

前言

讲完epoll,再结合自己最近学的Nginx,可以看看nginx 为何要用到epoll,后面其实还有 reids 数据库 也要用到 epoll,现在暂时不讲。我们应该知道nginx不是多线程,而是多进程,单线程,而且nginx通常适用于高并发场景,单线程怎么处理高并发的,接下来就来读懂这后边的秘密吧

Nginx 核心架构

Nginx 不是多线程模型,而是:多进程 + 单线程事件循环(Reactor 模型)

每个 worker 进程:

  • 只有 一个线程
  • 一个 事件循环(event loop)
  • 同时处理成千上万连接(10w+)

想想如果用select/poll 会怎么样?那简直就是灾难

假设10 万个连接,只有 100 个有数据

select / poll 每次:

  1. 用户态传 10 万 fd
  2. 内核遍历 10 万 fd
  3. 找出 100 个活跃 fd

结果:99.9% 都是无效扫描,CPU 被白白浪费,nginx单线程直接被拖死了

而epoll 的事件驱动机制和回调机制只处理了10万个连接中的100个有数据的连接

单线程 + epoll 的优势

很多人的误区:“多线程不是更快吗?”

在 Nginx 场景下,恰恰相反

1. 避免线程切换

多线程问题:

  • 上下文切换(CPU 开销)
  • 锁竞争

epoll + 单线程:

  • 无锁
  • 无切换

2. IO 不阻塞

Nginx 使用:非阻塞 IO + epoll

流程:

  • accept 新连接
  • 注册 epoll
  • 有数据才处理
  • 没数据立即返回

整个 worker 永远不会被一个连接卡住

Nginx + epoll 完整工作流程

  1. 首先 worker 创建 epoll 实例
  2. 将 fd 加入红黑树
  3. 当数据到达时,网卡中断,内核协议栈,socket buffer写入,内核调用 fd 对应的回调机制(将 fd 加入到 ready list 就序链表)
  4. 最后 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(边缘触发),只通知一遍
觉得有帮助可以投喂下博主哦~感谢!
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇