swoole通过reactor/worker分离模型实现高并发:reactor线程独占epoll/kqueue实例,阻塞等待i/o就绪后仅投递任务至worker,php回调在worker中执行;协程需避免阻塞调用,普通文件io不支持epoll,应交由task worker处理。

要让Swoole服务器真正响应成千上万并发连接而不卡死,必须理解它底层如何用epoll(Linux)或kqueue(macOS/BSD)驱动事件循环——这不是PHP层的轮询模拟,而是直接对接内核事件通知机制。
事件循环的本质是Reactor线程绑定一个epoll/kqueue实例
启动Swoole Server时,每个Reactor线程会初始化并独占一个epoll(或kqueue)实例;这个实例不是全局共享的,而是线程私有,避免锁竞争。
所有监听的socket fd(如主监听fd、客户端连接fd)都会通过swoole_event_add注册进该epoll实例,并指定read/write/error事件对应的C层回调函数。
Reactor线程随后进入阻塞调用epoll_wait()(或kqueue()),**此时线程真正挂起,CPU不空转**——只有当至少一个fd就绪,内核才唤醒该线程并返回就绪fd列表。
事件就绪后,不直接执行PHP回调,而是交由调度器分发
epoll_wait返回后,Reactor线程遍历就绪fd列表,对每个fd触发预先绑定的C函数(例如php_swoole_onRead)。
这些C函数不做业务处理,只做两件事:① 将fd关联的PHP回调(如onReceive)打包为任务;② 把任务推入当前Worker进程的任务队列。
【关键点】PHP回调实际在Worker进程中执行,Reactor线程绝不运行用户PHP代码——这是Reactor/Worker分离模型的核心隔离设计。
协程如何与事件循环协同工作
当协程调用Co::sleep(1),底层向EventLoop的定时器模块注册一个1秒后触发的事件,然后立即yield,控制权交还给调度器;此时Reactor线程仍在epoll_wait中等待I/O,不受影响。
但若协程中误用usleep(1000000),会直接陷入系统级nanosleep,整个Worker进程卡死——因为事件循环无法被唤醒,后续所有I/O事件堆积,连接超时断开。
Swoole 6.1.1 是一个专为 PHP 设计的高性能事件驱动并发网络引擎。作为稳定版,它修复了编译时对 zlib 依赖的缺失及 curl 模块的内存安全风险。该版本支持协程、多线程与多进程架构,内置 TCP/HTTP/WebSocket 服务器,能够显著提升 PHP 在微服务、实时通信等场景下的执行效率与并发能力。
file_get_contents默认仍是阻塞调用,除非提前调用Runtime::enableCoroutine()启用hook,否则协程遇到它会同步阻塞,等同于usleep效果。
为什么普通文件IO在epoll中无效
epoll仅对支持事件通知的fd类型有效(如socket、pipe、eventfd),对regular file(如/tmp/log.txt)完全无感知。
当你对普通文件调用Co::readFile,Swoole检测到fd不支持epoll,自动退化为线程池+阻塞读——这反而增加上下文切换开销,性能低于同步读。
所以【不要对磁盘文件使用协程IO】,这类操作应放在Task Worker中异步处理,或直接用同步方式在Worker中完成。
事件循环生命周期的关键节点
第一步:Server启动时,Master进程fork出Reactor线程和Worker进程;每个Reactor线程初始化自己的epoll实例。
第二步:Reactor线程调用listen()创建监听socket,设置为非阻塞模式,再用epoll_ctl(ADD)将其加入epoll监听集。
第三步:客户端connect触发EPOLLIN事件,Reactor线程accept新fd,同样设为非阻塞,并epoll_ctl(ADD)注册read事件。
第四步:数据到达时,内核标记该fd可读,epoll_wait返回,Reactor线程触发read回调,将数据读入缓冲区并投递到Worker。
第五步:Worker进程处理完请求,调用$server->send(),Reactor线程监听该fd的EPOLLOUT事件,待内核通知可写时再真正发出数据。










