协程死循环不会报错也不吃CPU,因为Swoole默认不检测协程是否长时间不yield,调度仅发生在I/O操作、显式yield或超时点;纯PHP while(true)会卡住协程但不进内核态,表现为request_count停滞、连接堆积、tasking_num为0。

协程死循环为什么不会报错也不会吃CPU
因为 Swoole 默认不检测协程是否长时间不 yield,调度器只在 I/O 操作(如 co::sleep、co::readFile)、显式 Co::yield() 或超时点才切换协程。一个纯 PHP 层的 while (true) { } 或 while ($flag) { /* 无 yield */ } 会一直占用当前协程,但既不触发系统调用,也不进内核态,所以 top 看不到 CPU 升高,只表现为:request_count 停滞、连接堆积、tasking_num 持续为 0。
必须手动开启 deadlock_check 才能捕获死循环
Co::set(['deadlock_check' => true]) 是唯一能主动发现“卡住协程”的配置,它不是杀掉协程,而是在超过阈值后打一条 WARNING 日志。关键点:
-
deadlock_timeout建议设为2000~5000,太小会误报密集计算,太大失去意义 - 该配置只对当前
Co::run()或当前 Worker 进程生命周期有效;Server 模式下需在onWorkerStart中重复设置 - 确保
swoole.log_level >= 3,否则日志被过滤,根本看不到 warning -
Co::set()必须在Co::run()或 Server 启动前调用,否则无效
哪些操作看似安全实则隐含死循环风险
不是只有 while(true) 才危险,以下常见写法同样卡住协程且不易察觉:
Swoole 6.1.1 是一个专为 PHP 设计的高性能事件驱动并发网络引擎。作为稳定版,它修复了编译时对 zlib 依赖的缺失及 curl 模块的内存安全风险。该版本支持协程、多线程与多进程架构,内置 TCP/HTTP/WebSocket 服务器,能够显著提升 PHP 在微服务、实时通信等场景下的执行效率与并发能力。
-
file_get_contents('http://...'):未启用 hook(SWOOLE_HOOK_ALL)时,仍是同步阻塞调用,不 yield -
curl_exec($ch):没设CURLOPT_TIMEOUT_MS,可能无限等待 -
session_start():文件锁阻塞,后续请求卡在加锁环节,非协程可控 -
flock($fp, LOCK_EX):若前一个进程异常退出未释放锁,新协程会永久阻塞 - Redis 同步客户端(如
phpredis):不支持协程,每次调用都阻塞整个协程
真正防死循环要靠三层防护
单靠 deadlock_check 只是兜底,不能替代编码规范:
- 所有 I/O 操作前,确认已启用完整 hook:
Swoole\Coroutine::set(['hook_flags' => SWOOLE_HOOK_ALL]) - 禁用任何同步阻塞函数(
sleep()、usleep()、file_get_contents()),改用co::sleep()、co::readFile()等协程版 - 对无法协程化的操作(如图像处理、某些 SDK),必须投递到
$server->task(),隔离到 Task 进程
最易被忽略的是:hook 必须在 Server 启动前、甚至 Co::run() 外就设置好;一旦漏掉,后面所有协程 I/O 都退化成同步阻塞——此时 deadlock_check 也救不了你。

















