worker_processes 决定并行 epoll 实例数,每个 worker 独立创建 epoll fd、事件数组及处理逻辑;设为 auto 可自动匹配 cpu 核心数,过高或过低均降低性能。

worker_processes 不是 epoll 的开关,而是决定“有多少个独立 epoll 实例并行工作”的关键配置。每个 worker 进程在启动时自建一套 epoll 环境——包括自己的 epoll fd、就绪事件数组、事件注册表和处理逻辑。它们彼此隔离,互不干扰。
每个 worker 对应一个专属 epoll 实例
master 进程完成 socket 的 bind 和 listen 后,fork 出多个 worker。每个 worker 在初始化函数 ngx_worker_process_init() 中调用 ngx_epoll_init(),执行三件事:
- 调用
epoll_create1(0)创建独属的 epoll 文件描述符,保存在当前 cycle 的 files 数组中 - 分配默认 512 项的就绪事件缓冲区(可由
epoll_events指令调整) - 将全局事件操作接口
ngx_event_actions指向 epoll 分支,确保后续所有add/del/mod/wait都走 epoll 系统调用
这意味着:设 worker_processes 4,就有 4 个完全独立的 epoll 实例;哪怕其中某个 worker 正在处理 10 万连接,其余 3 个仍可各自高效响应自己的就绪事件。
连接分配与事件生命周期全程封闭在单 worker 内
新连接如何落到某个 worker?取决于内核层策略(如 SO_REUSEPORT)或 Nginx 自身的 accept_mutex 机制。一旦某个 worker 成功 accept() 到连接,该连接的整个生命周期——从注册 EPOLLIN/EPOLLET、读请求头、解析 body、写响应、到关闭——全部由该 worker 独立完成:
- 监听 socket 在初始化时就被注册为 EPOLLIN,等待新连接
- accept() 后的新 client fd 立即以边缘触发方式注册 EPOLLIN | EPOLLET
- 发送响应时仅当 send() 返回 EAGAIN 才临时注册 EPOLLOUT,发完立刻注销,避免空转触发
- 所有事件回调(如
rev->handler或wev->handler)都在本 worker 上下文中执行,不跨进程同步状态
worker_processes 设置不当会直接削弱 epoll 效能
epoll 本身是 O(1) 就绪检测,但它的吞吐上限受制于 worker 进程数与硬件资源的匹配程度:
- 设得太少(如 8 核机器只配 2 个 worker),等于让 2 个 epoll 实例扛全部流量,CPU 利用率不均,瓶颈明显
- 设得太多(如 8 核配 16 个 worker),引发频繁上下文切换和锁竞争(如 accept_mutex 抢占加剧),反而降低整体事件处理效率
- 推荐值为
worker_processes auto;,Nginx 会读取/proc/cpuinfo自动匹配物理核心数;高负载场景下可尝试设为 CPU 核心数 × 1.5,但需实测验证
配置必须与系统限制协同生效
光有合理的 worker_processes 不够,还需配套调整:
-
worker_connections要与ulimit -n一致,否则 epoll_wait 可能因 fd 不足而失败 - 显式写
use epoll;在 events 块中,避免容器环境或旧内核下 fallback 到低效的 select/poll -
multi_accept on;让单次 epoll_wait 触发后尽可能多地 accept 新连接,减少事件循环唤醒次数
epoll 的高效,从来不是靠“一个实例管所有”,而是靠“每个 worker 各管一摊、各建一套、各干各的”。worker_processes 就是这个分工体系的总开关。











