sleep() 会阻塞整个协程调度,导致 worker 假死;co::sleep() 是协程安全的定时等待,通过 reactor 注册任务实现非阻塞挂起与唤醒。

在 Hyperf 3.1(基于 Swoole 协程)中,sleep() 和 co::sleep() 表现截然不同:前者会破坏协程调度能力,后者才是协程安全的等待方式。
sleep() 为什么不能在协程里用
PHP 原生 sleep() 是同步阻塞调用,底层触发系统级休眠(如 nanosleep),直接挂起当前线程。而 Hyperf 的协程运行在单线程事件循环之上——一旦该线程被阻塞,整个 worker 进程的事件循环就停摆,所有协程、网络 IO、定时器全部冻结。
- 哪怕只调用
sleep(0.001),也会中断调度周期,导致其他协程延迟执行甚至超时 - 实测 QPS 可能下降 50% 以上,高并发下极易引发“假死”现象
- 错误常藏在日志重试、SDK 封装层或测试脚本中,不易察觉
co::sleep() 是怎么工作的
co::sleep() 不是“协程版 sleep”,而是向 Swoole reactor 注册一个毫秒级定时任务。它立即返回,当前协程进入 suspended 状态,控制权交还给事件循环,其他协程照常运行;到期后由内核自动唤醒该协程。
- 参数单位是秒(支持浮点数),例如
co::sleep(0.1)表示等待 100ms - 精度约 10ms,依赖 reactor 负载,不保证严格准时,但完全不阻塞调度
- 必须运行在协程上下文(如
go(function () { })内),否则会报错
常见误用与排查建议
协程卡死、CPU 占用极低、接口无响应,大概率是误用了 sleep() 或其他同步 IO(如 file_get_contents、curl_exec)。
- 用
swoole_get_local_cid()检查:返回 -1 表示已脱离协程上下文 - 通过
Coroutine::listCoroutines()+Coroutine::getStatus()找出状态为SWOOLE_CORO_WAITING的协程 - 配合
Coroutine::getBackTrace()查看堆栈,重点确认是否停留在stream_select、socket_read等同步调用上
替代方案不只是 sleep
真正需要等待时,优先考虑业务逻辑驱动的异步等待,而非人为延时:
- 数据库操作用
Hyperf\Database\Connection或 Eloquent,避免原生 PDO - HTTP 请求用
Hyperf\HttpClient\Client::get(),而非file_get_contents - 若确需延时(如退避重试),统一使用
co::sleep(),并注意参数单位











