swoole默认不检测协程死循环,需主动启用deadlock_check;因调度器仅在i/o、yield或超时点切换协程,空while等忙等不触发调度,故无报错、cpu不显高,仅表现为连接堆积、request_count停滞。

协程死循环不会自动被 Swoole 检测或中断,必须主动启用死锁/阻塞检测机制,否则卡住的协程会一直占用资源、拖垮整个服务。
为什么 go() 卡住却没报错?
Swoole 默认不开启协程死锁检测——它把“长时间不 yield”视为合法行为(比如你在协程里写了个 while (true) { usleep(1000); })。系统级工具(如 top)也看不到 CPU 占用,因为这是纯 PHP 层忙等,没进内核态。真实现象是:连接堆积、tasking_num 持续为 0、request_count 停滞,但 Worker 进程状态正常。
- 根本原因:Swoole 协程调度器只在遇到 I/O 操作(如
co::sleep、co::readFile)、显式yield()或超时点才会切换协程 - 常见诱因:
file_get_contents(未 hook)、curl_exec(无 timeout)、session_start()文件锁等待、空while循环、Redis 同步调用 - 注意:
Swoole\Coroutine::set(['hook_flags' => SWOOLE_HOOK_ALL])必须在Co\run()或 Server 启动前执行,否则 I/O 不会被协程化,也就无法触发调度点
Co\run() 中如何启用死循环检测
在协程入口函数中,用 Co::set() 开启 deadlock_check 并设合理阈值(单位:毫秒),这是最轻量、最直接的方式:
Swoole 6.1.1 是一个专为 PHP 设计的高性能事件驱动并发网络引擎。作为稳定版,它修复了编译时对 zlib 依赖的缺失及 curl 模块的内存安全风险。该版本支持协程、多线程与多进程架构,内置 TCP/HTTP/WebSocket 服务器,能够显著提升 PHP 在微服务、实时通信等场景下的执行效率与并发能力。
Co::set([
'deadlock_check' => true,
'deadlock_timeout' => 3000, // 超过 3 秒不 yield 就报 warning
]);
Co::run(function () {
go(function () {
while (true) {
// 没有 yield / sleep / I/O —— 3 秒后会触发日志警告
}
});
});
-
deadlock_timeout建议设为 2000~5000,太小会误报(如密集计算),太大失去意义 - 该配置仅对当前
Co::run()生命周期有效,Server 模式下需在onStart或onWorkerStart中重复设置 - 警告日志级别默认为
SWOOLE_LOG_WARNING,确保swoole.log_level≥ 3,否则看不到
HTTP Server 场景下怎么加检测
Server 启动后所有请求协程都继承全局配置,但需在 onWorkerStart 中显式设置,否则新 Worker 进程不生效:
$server = new Swoole\Http\Server('0.0.0.0', 9501);
$server->on('workerStart', function ($server, $worker_id) {
Swoole\Coroutine::set([
'deadlock_check' => true,
'deadlock_timeout' => 3000,
'hook_flags' => SWOOLE_HOOK_ALL,
]);
});
- 别在
onStart设置——它运行在 Manager 进程,不影响 Worker - 如果用了
task_worker_num > 0,也要在onTask回调开头手动Co::set(),因为 task 进程不共享 Worker 配置 - 配合
swoole.log_file和磁盘监控,避免日志写满导致检测失效(常见坑:日志路径不可写,错误静默丢弃)
死循环检测不是万能的,这些情况它发现不了
deadlock_check 只监控“无 yield 时间”,对以下场景完全无效:
- 协程里调用阻塞式 C 扩展(如某些未 hook 的 Redis 扩展、pdo_mysql 同步模式)
- PHP 层死锁:两个协程互相
wait对方 channel,但各自都还活着、仍在 yield - 无限递归(爆栈前就 crash,不走 deadlock 检测路径)
- 协程内
exit()或 fatal error 导致提前退出,检测逻辑根本没机会跑
真正要定位这类问题,得靠 Swoole\Coroutine::listCoroutines() + Swoole\Coroutine::getBackTrace($cid) 抓堆栈,或者用 swoole-tracker 做实时火焰图——死循环检测只是第一道防线,不是终点。










