nginx事件循环是基于i/o多路复用的多阶段分层调度系统,信号由独立线程捕获后仅设标志并唤醒主循环,事件按内核就绪顺序同步处理,定时器用红黑树管理,全程单线程无锁。

Nginx 的事件循环(Event Loop)并不采用传统单线程 JavaScript 那样的“一个队列、一个循环”模型,而是基于操作系统底层 I/O 多路复用机制(如 epoll、kqueue、select)构建的**多阶段、分层响应式调度系统**。它同时处理网络事件、定时器和信号,但三者路径不同、优先级有别、触发时机分离。
信号由独立信号处理线程捕获,不进入主 event loop
Nginx 主进程在启动时会调用 sigwait() 或 sigprocmask() + sigwaitinfo() 等系统调用,将所有关注的信号(如 SIGHUP、SIGUSR2、SIGTERM)屏蔽,并在一个专用的信号等待循环中同步接收。这个循环通常运行在主线程或单独创建的信号处理线程中,与负责网络 I/O 的 event loop 物理隔离。
- 收到信号后,Nginx 不直接执行重载/退出逻辑,而是向 event loop 发送一个“唤醒通知”(例如通过
eventfd或pipe写入一个字节) - 主 event loop 在下一次
epoll_wait()返回前或返回后检查该通知,然后调用对应 handler(如ngx_signal_handler) - 这种设计避免了信号中断系统调用导致的竞态,也防止信号处理函数中调用非异步信号安全函数(如 malloc、printf)
事件队列实际是“就绪列表”,而非 FIFO 队列
Nginx 不维护一个全局事件队列(如 libuv 的 pending queue),而是依赖内核返回的就绪文件描述符列表(如 epoll_wait() 返回的 struct epoll_event[])。这些就绪事件按内核调度顺序排列,Nginx 按顺序逐个调用其回调函数(read_handler / write_handler)。
- 每个连接(
ngx_connection_t)自带读写事件结构体,绑定到 epoll 实例,不额外排队 - 定时器使用红黑树组织(
ngx_rbtree_t),每次 event loop 迭代前检查最小超时节点,触发到期 timer handler - accept、read、write 等事件的处理是同步执行的,没有“入队→出队→执行”的中间缓冲
主循环结构:阻塞等待 → 处理就绪事件 → 执行定时器 → 检查退出信号
典型 worker 进程的 event loop 主干如下(简化):
- 调用
epoll_wait()(带超时,通常为当前最近 timer 的剩余时间) - 若超时返回,说明无 I/O 就绪,直接跳转到 timer 处理;若返回就绪事件,则遍历并调用各事件 handler
- 遍历红黑树,执行所有已到期的 timer(
ngx_event_expire_timers) - 检查是否收到 “quit/shutdown/reload” 标志(来自信号处理模块设置的全局变量),决定是否退出循环
这个流程确保 I/O 响应及时、timer 精度可控、信号响应可预测,且所有操作都在单线程内完成,无需锁竞争。
关键细节:信号与事件的协同靠“标志位 + 唤醒”
Nginx 不让信号 handler 做实质工作,只做两件事:设置全局标志(如 ngx_terminate、ngx_reconfigure)和触发 event loop 唤醒。唤醒机制因平台而异:
- Linux 上常用
eventfd:信号 handler 向 eventfd 写入 8 字节,主循环的 epoll 实例监听该 fd - FreeBSD/macOS 可用
kevent直接注册EVFILT_SIGNAL - 其他系统退化为
pipe+EPOLLIN监听 - worker 进程收到唤醒后,在下次循环开始时检查标志位并执行相应逻辑(如关闭监听 socket、重新加载配置)
这种解耦设计使信号处理既安全又可控,也解释了为什么发送 kill -HUP 后配置并非立即生效——要等当前请求处理完、本次 event loop 迭代结束才真正 reload。











