Node.js事件循环是单线程分阶段调度,Swoole是多进程+协程混合模型:前者严格按timers→pending callbacks→idle/prepare→poll→check→close callbacks六阶段推进;后者由C实现的epoll/kqueue驱动,协程调度器接管PHP控制流,无微/宏任务之分,依赖I/O阻塞点触发切换。
Node.js 的事件循环运行在单个 V8 实例中,严格按 timers → pending callbacks → idle/prepare → poll → check → close callbacks 六个阶段推进,每个阶段处理对应队列里的回调。而 Swoole 的事件循环不依赖 JS 引擎,底层用 C 实现的 epoll/kqueue 监听 I/O,但它的“循环”本身不暴露给 PHP 层——PHP 代码运行在 Worker 进程里,由协程调度器(SwooleCoroutineScheduler)接管控制流。
这意味着:
setTimeout(fn, 0) 不一定立刻执行,要等完当前宏任务 + 所有微任务 + 进入下一轮 timers 阶段;Swoole 中 SwooleCoroutine::sleep(0) 会立即让出协程,但不触发系统级调度延迟process.nextTick 和 Promise.then 都属于微任务,优先级高于 setTimeout;Swoole 没有微/宏任务之分,协程切换由 I/O 阻塞点(如 co::sleep、mysql->query、http_client->get)或显式 Co::yield() 触发cluster fork 子进程,父子进程间通信成本高;Swoole 默认启动多个 Worker 进程,每个进程内可并发跑数百协程,无锁共享数据靠 SwooleTable 或 SwooleAtomic
直接把 Node.js 风格的嵌套回调(比如 fs.readFile 套 mysql.query 套 http.request)搬进 Swoole,大概率会卡死或报 ERROR swManager_loop: fatal error: manager process exit, status=0, signal=0。因为 Swoole 的异步 API(如 SwooleCoroutineMySQL)本质是协程友好的阻塞调用,不是 Node.js 那种纯回调驱动。
正确做法是用同步风格写协程逻辑:
onConnect/onReceive 回调,改用 SwooleCoroutineHttpServer + go(function () { ... })
file_get_contents 在协程上下文里会自动变成非阻塞,但前提是文件路径不能是本地磁盘(需用 SwooleCoroutineFileSystem);HTTP 请求必须用 SwooleCoroutineHttpClient,不能用 curl_exec
SwooleCoroutineMySQL 或 SwooleCoroutineRedis,它们内部已封装了连接池和协程挂起逻辑,不需要手写 onClose 或 once('data')
Node.js 的 setTimeout 在空闲时能稳定做到 ±1ms 误差;Swoole 的 SwooleTimer::tick 或 SwooleCoroutine::sleep 实际精度受 Worker 进程负载影响——如果某个协程正在执行 CPU 密集型操作(如大数组排序),定时器回调可能被延后数毫秒甚至更久。
关键约束:
SwooleTimer::after 注册的回调在主线程(Manager 进程)执行,不能访问协程上下文变量;必须用 go(function () { Co::sleep(...) }) 在协程内实现延迟SwooleCoroutine::defer + 循环 Co::sleep(10),并检查实际耗时做补偿setImmediate 类似于 Swoole 的 Co::defer,但后者只在当前协程结束前执行,不跨协程生效Node.js 中未捕获的 Promise rejection 会触发 unhandledRejection 事件,进程默认不退出;Swoole 中协程内抛出未捕获异常,会直接终止该协程,但不影响其他协程或 Worker 进程。然而,如果在 onRequest 回调里没包 try/catch,整个请求生命周期就断了,客户端收不到响应。
常见翻车点:
mysqli_query)发生错误,不会被 catch 捕获,因为那根本不在协程调度路径上SwooleCoroutineMySQL->query 出错时返回 false 而非抛异常,必须手动检查 $mysql->errno,不像 Node.js 的 mysql2 默认 rejectCo::set(['hook_flags' => SWOOLE_HOOK_ALL]) 后,部分系统调用(如 curl_init)会被协程化,但错误码映射不一致,curl_error 可能为空,得看 curl_errno
最易被忽略的是:Swoole 的事件循环不处理 PHP 的 register_shutdown_function,协程内 exit 会导致整个 Worker 进程崩溃;而 Node.js 的 process.exit 只终止当前实例。线上环境务必禁用所有隐式 exit 和未兜底的 die。