协程不是自动异步,必须显式启用swoole\runtime::enablecoroutine(swoole_hook_all)才能使sleep等函数非阻塞;否则原生sleep会同步阻塞整个worker进程,导致并发归零。

协程不是“自动异步”,必须显式启用 Runtime Hook 才能让 file_get_contents、curl_exec、sleep 等函数真正非阻塞;否则整个 Worker 进程会被卡死,并发数直接归零。
为什么 sleep(1) 在协程里会阻塞其他请求?
因为 PHP 原生 sleep() 是同步阻塞系统调用,Swoole 默认不会接管它。协程调度器只对被 Hook 的函数生效。
- 必须在 Server 启动前(如
onWorkerStart中)调用Swoole\Runtime::enableCoroutine(SWOOLE_HOOK_ALL) - 若只启用了
SWOOLE_HOOK_IO,sleep()仍阻塞;SWOOLE_HOOK_ALL才覆盖sleep、usleep、time_nanosleep等 - 验证方式:在协程中并行执行
sleep(1)和Co::sleep(1),用microtime(true)测总耗时 —— 若接近 2 秒,说明原生sleep未被 Hook
为什么全局变量在 onRequest 里越累越大?
PHP-FPM 每次请求新建进程,全局变量自然重置;Swoole Worker 进程常驻,global $counter 或 static $i = 0 会在多次请求间持续存在,形成状态污染。
Swoole 6.1.1 是一个专为 PHP 设计的高性能事件驱动并发网络引擎。作为稳定版,它修复了编译时对 zlib 依赖的缺失及 curl 模块的内存安全风险。该版本支持协程、多线程与多进程架构,内置 TCP/HTTP/WebSocket 服务器,能够显著提升 PHP 在微服务、实时通信等场景下的执行效率与并发能力。
- 这不是 Bug,是模型差异:Swoole 的生命周期以进程为单位,而非请求
- 错误写法:
static $req_id = 0; $req_id++;→ 每次请求都递增,最终溢出 - 正确替代:用
Swoole\Table存储请求 ID(需预定义列)、或用$_SERVER['request_time_float']+$fd构造唯一键 - 更安全做法:彻底避免全局/静态变量,所有上下文数据通过函数参数传递
为什么连接池没生效,还是频繁重建 MySQL 实例?
协程客户端复用的前提是:实例不能被 GC 回收,且必须手动归还;直接 new 一个 Swoole\Coroutine\MySQL 并在函数结束时离开作用域,就会触发析构并关闭底层连接。
- 连接池必须自己管理生命周期:预创建固定数量实例,存入
Swoole\Coroutine\Channel,取用后必须$channel->push($client)归还 - 忘记归还 = 连接泄漏;归还前未调用
$client->close()(如果已异常)= 下次取出即不可用 - 检查点:用
swoole_server->stats()查看connection_num是否稳定;突增说明连接未复用 - 注意:PDO/MySQLi 原生扩展不支持协程,强行用会导致 Worker 阻塞,必须换
Swoole\Coroutine\MySQL
为什么热更新后新代码不执行?
Swoole 不像 FPM 那样每次请求 reload PHP 文件;类、函数一旦被加载进内存,就不会重新解析,除非 Worker 进程重启。
-
opcache.enable_cli=1会缓存 CLI 模式下的脚本字节码,导致修改后仍执行旧逻辑 —— 必须设为0 - Composer autoload 类在
onWorkerStart外加载的,reload 后依然用旧版本;应把require或new放到onWorkerStart内部 - 柔性 reload(
reload_async=1+max_wait_time)能保证旧 Worker 处理完正在运行的协程再退出,但不会重载已加载的类 - 真正生效的方式只有两种:改完代码后发
kill -USR1触发 reload,或直接kill -TERM全量重启
最易被忽略的点是 Hook 的启用时机和范围 —— 它必须在任何协程启动前完成,且要覆盖业务中所有隐式 IO 调用,比如日志写文件、配置读取、甚至某些 SDK 内部的 file_get_contents。漏掉一个,就可能让整个并发模型坍塌。










