nginx单线程处理上万并发的核心在于“不等”:通过非阻塞i/o、状态机驱动和epoll事件循环,让一个worker进程高效轮询成千上万个连接,仅在就绪时处理,避免阻塞、线程切换与资源浪费。

单线程处理上万并发,不是靠“快”,而是靠“不等”。Nginx 的核心不在算力多强,而在它从不把时间浪费在空等上——非阻塞 I/O 搭配状态机驱动,让一个 worker 进程像一位超级前台,同时盯住成千上万个连接的状态变化,只在真正有事可做时才出手。
非阻塞 I/O:拒绝傻等,随时切换
传统服务器调用 read() 或 accept() 时,若数据没来、连接没建好,进程就卡住不动,进入阻塞态。Nginx 不这么干:它把 socket 设为非阻塞模式,每次系统调用都立即返回。没数据?返回 EAGAIN 或 EWOULDBLOCK;连接未就绪?照常返回错误,但不挂起。这就像前台看到客人还没举手,不站着干等,而是转身去盯下一张桌。
- 所有 socket 都设为 O_NONBLOCK
- 每次 I/O 操作都带错误检查,只处理“就绪”事件
- 内核通过 epoll_wait()(Linux)批量通知哪些 fd 可读/可写/出错
状态机:每个连接都有自己的“当前进度”
Nginx 不为每个连接分配线程或栈,而是为每个连接维护一个轻量级结构体(ngx_connection_t),里面存着它的当前状态,比如:NGX_HTTP_READING_REQUEST、NGX_HTTP_PROCESS_REQUEST、NGX_HTTP_SENDING_RESPONSE。一次请求的生命周期被拆成若干明确阶段,每轮事件循环只推进那些“已就绪”的连接到下一状态。
- 新连接接入 → 状态设为“等待接收请求头”
- epoll 通知该连接有数据可读 → 切入“解析请求行”状态
- 请求头收全 → 跳转“查找 location / 处理静态文件”状态
- 磁盘读取中 → 状态切为“等待文件 I/O 完成”,然后继续处理其他连接
事件循环 + 多路复用:单线程的调度中枢
每个 worker 进程运行一个无限循环:epoll_wait() → 遍历就绪事件列表 → 根据连接状态分发处理函数 → 返回继续等待。这个循环本身不耗时,也不阻塞,它只是“看一眼、动一下、再看一眼”。哪怕有 10 万个连接,只要其中只有 200 个此刻有数据到达,Nginx 就只花精力处理这 200 个,其余 99800 个静静待在 epoll 表里,不占 CPU、不占栈空间、不触发上下文切换。
- epoll 是内核提供的高效就绪列表,O(1) 查找,无遍历开销
- 状态迁移由回调函数驱动(如 ngx_http_process_request_line),逻辑清晰可测
- 没有线程锁、没有共享栈、没有协程调度器,纯 C 函数跳转,极简可靠
为什么不用多线程反而更稳更快?
多线程模型下,每个连接绑定一个线程,看似直观,实则代价隐性而巨大:10 万连接 ≈ 10 万线程,光是线程栈就吃掉数 GB 内存;CPU 在这些线程间疯狂切换,缓存失效严重;一旦某线程阻塞(比如慢磁盘读),整个线程就废了。Nginx 把“等待”从执行流中剥离出来,交由内核事件机制托管,自己只做确定性工作——解析、路由、组装响应、发回包。所有不确定性的等待(网络延迟、磁盘 IO、后端超时),都退回到事件表,不占用任何执行资源。
- 内存占用稳定:worker 进程数固定(通常等于 CPU 核数),每个连接仅需几 KB 元数据
- CPU 利用率高:几乎没有空转或上下文切换损耗
- 故障隔离好:一个连接状态机出错,不影响其他连接,也不会拖垮整个 worker











