Nginx 的 Event Loop 通过单线程非阻塞 I/O 和 epoll/kqueue 批量事件监听,将上下文切换从“按连接”压缩为“按事件批次”,显著降低调度开销;配合 CPU 绑定、高 fd 限制和零拷贝等优化,才能充分发挥其性能优势。

Nginx 的 Event Loop 是减少上下文切换开销的核心机制,它不靠增加线程数量应对并发,而是让每个 worker 进程用一个单线程持续轮询事件、处理就绪 I/O,把“等待”从内核调度行为转为用户态逻辑判断。
Event Loop 基于非阻塞 I/O 批量监听
传统阻塞模型中,每个 accept() 或 read() 调用若无数据就绪,进程会被挂起,触发一次完整上下文切换;而 Event Loop 使用 epoll_wait()(Linux)或 kqueue(BSD/macOS),一次系统调用就能同时检查成千上万个 socket 是否就绪。只有真正有事件时才返回,其余时间 worker 进程保持运行,不主动让出 CPU,避免了“空等”带来的无效切换。
- 所有 socket 都设为 O_NONBLOCK,确保 recv/send 立即返回(EAGAIN/EWOULDBLOCK),不会卡住事件循环
- epoll 实例在 worker 启动时创建并复用,避免反复创建销毁开销
- 事件就绪后,Nginx 在用户态顺序执行 accept → recv → parse → send,全程不离开用户空间
单线程事件循环替代多线程模型
对比 Apache prefork 模型——每连接一个进程,数千连接就带来数千调度单元——Nginx 默认只启 4–8 个 worker 进程(通常等于物理核心数)。每个 worker 内部是单线程 Event Loop,管理数万连接。内核只需维护少量进程上下文,调度频次大幅下降。
- 没有线程栈分配与切换,每个连接仅占用 KB 级内存(buffer + connection struct)
- worker 进程间无共享连接状态,天然避免锁竞争和跨线程同步开销
- 慢操作(如 upstream 代理)通过异步子请求或延迟回调处理,主线程不阻塞
上下文切换从“按连接”压缩为“按事件批次”
在高并发场景下,10 万连接并不意味着 10 万次上下文切换。Event Loop 让切换频次与活跃事件数强相关:一次 epoll_wait() 返回 50 个就绪 fd,Nginx 就批量处理这 50 个事件,期间只发生 1 次系统调用进出,最多 2 次上下文切换(进入内核 + 返回用户态)。
- 压测显示,合理配置下每秒上下文切换可稳定在 10 万次以内,远低于 fork/prefork 模型的线性增长
- 若后端响应慢或 keepalive 未启用,worker 可能长时间持有连接,但仍是用户态内部状态机流转,不触发新调度
- 搭配 sendfile 零拷贝后,静态文件路径甚至无需进入用户态,进一步削减切换次数
必须配合 CPU 绑定与资源供给才真正生效
Event Loop 的低切换优势不是自动兑现的。如果 worker 进程被 OS 调度器频繁迁移跨核,或因文件描述符不足退化为轮询,效果就会打折。
- 启用 worker_processes auto; 和 worker_cpu_affinity auto;,确保每个 worker 稳定绑定独立物理核
- 设置 worker_rlimit_nofile 65535,并同步调整 /etc/security/limits.conf,防止 fd 耗尽引发异常重试
- 确认使用 use epoll;(Linux)且未降级到 select/poll,可通过 nginx -V 验证编译选项


















