协程栈本身不是内存增长主因,真正泄漏源是全局变量、静态属性及未释放资源;需避免全局写入、显式清理连接、合理配置hook与max_request并监控rss。

协程栈默认 2KB~8KB,但全局变量、静态属性、未释放的连接句柄会持续累积——这才是内存增长的主因,不是协程本身。
协程栈分配机制与实际占用
Swoole 协程启动时按需分配独立栈空间,默认最小 2KB,最大 8KB(可通过 coroutine.stack_size 配置项调整)。这个栈只存函数调用帧、局部变量和寄存器上下文,不包含对象实例或大数组。
常见误解是“协程越多内存越高”,其实单个协程开销极小;真正危险的是在协程内反复 new 对象、读大文件、缓存未清理的结果集。
- 栈大小不影响 PHP 堆内存,
memory_get_usage()统计不到它 - 若业务中频繁创建大数组(如
range(1, 100000)),这部分走的是 PHP 堆,必须靠unset或作用域自动回收 - 调试时可用
swoole_memory_dump()查看当前所有协程栈使用量,但更关键的是堆内存快照
全局变量/静态属性泄漏的典型场景
Worker 进程常驻,$GLOBALS、static 属性、类常量、闭包引用的外部变量都不会被自动清空。一次请求写入,后续所有请求都可见。
比如在 onRequest 回调里执行:
Swoole 6.1.1 是一个专为 PHP 设计的高性能事件驱动并发网络引擎。作为稳定版,它修复了编译时对 zlib 依赖的缺失及 curl 模块的内存安全风险。该版本支持协程、多线程与多进程架构,内置 TCP/HTTP/WebSocket 服务器,能够显著提升 PHP 在微服务、实时通信等场景下的执行效率与并发能力。
$GLOBALS['cache'][] = $result; MyClass::$buffer[] = $data;
不出三五百次请求,内存就明显上涨。
- 禁止在回调中向全局或静态容器追加数据,改用函数局部变量
- 若必须缓存,限定生命周期:用
Co::getUid()做 key 存到Swoole\Table或chan,并在请求结束前显式del或pop - 检查第三方 SDK 是否偷偷用了
static缓存(如某些旧版 Redis 封装)
enable_coroutine 启用时机与 hook 漏洞
Swoole\Runtime::enableCoroutine() 必须在 Worker 启动后、任何 I/O 操作之前调用。放错位置会导致部分函数仍走同步阻塞路径,间接引发协程卡死、伪内存泄漏(表现为 CPU 空转、内存缓慢爬升)。
- 正确位置:在
onWorkerStart回调最开头调用,且仅调用一次 - 常见漏掉的阻塞点:
file_get_contents()、sleep()、PDO::__construct()、curl_init()—— 它们默认不被 hook,需显式启用SWOOLE_HOOK_FILE或SWOOLE_HOOK_CURL - 验证是否生效:在协程里跑
sleep(1),同时发起另一个 HTTP 请求,看后者是否被阻塞;阻塞说明 hook 未覆盖到位
max_request 不是万能解药
max_request 参数能让 Worker 处理指定请求数后退出,由 Manager 进程拉起新进程,从而强制释放所有长期持有的内存。但它有硬性限制:
- 仅对同步阻塞型 Server(如
Swoole\Http\Server)有效;纯异步 Server(如Swoole\WebSocket\Server)不适用 - Base 模式下完全无效,因为没有 Manager 进程参与重启
- 设得太小(如
100)会导致频繁 fork,反而增加系统负担;建议从3000起步,结合memory_get_peak_usage()日志动态调优 - 它掩盖问题而非解决问题——应优先定位哪个变量/对象在膨胀,而不是依赖定时重启
真正的内存治理,得盯住每次请求结束后还活着的东西。协程栈只是冰山一角,水面下是静态引用、连接池残留、未 close 的 PDO 实例,以及那些你以为“用完就扔”其实还挂在闭包里的大数据结构。










