hyperf没有独立协程调度器,其协程调度完全由swoole内核实现,通过事件循环管理协程栈的保存/恢复、状态机切换及yield/resume控制,hyperf仅封装swoole_coroutine_create等api。

协程调度器不是独立组件,而是 Swoole 内核行为
Hyperf 本身没有实现协程调度器——它完全依赖 swoole_coroutine_create 和底层协程运行时。所谓“Hyperf 的协程调度”,实际是 Swoole 在事件循环中对协程栈的保存/恢复、状态机切换和 yield/resume 控制。你在 src/coroutine/src/Coroutine.php 看到的 create 方法,只是对 swoole_coroutine_create 的封装,不参与调度逻辑。
常见误解是认为 Hyperf 有“自己的调度器”。事实上,只要调用 Co::sleep()、mysql_query()(协程版)、HttpClient->get() 等,底层都会触发 co_yield,交出控制权给 Swoole 的 event loop;等 IO 就绪后,Swoole 自动 resume 对应协程。这个过程对 Hyperf 完全透明。
容易踩的坑:
- 误以为可以手动控制协程“被调度”的时机——你只能触发让出(如
Co::usleep(1)),但无法指定下一个执行谁 - 在非协程环境(如 Task 进程、FPM 模式)里调用协程函数,会直接报
PHP Warning: Swoole\Coroutine::sleep(): must be called in the coroutine - 用
while(true)+ 纯计算不加 yield,会导致当前 Worker 线程彻底卡死,其他协程无法执行
用户态线程 = 协程,但不是“线程”
文档里常把协程称作“用户态线程”,这是类比说法,不是技术事实。协程没有操作系统线程 ID,不占用内核线程资源,也不受 pthread 调度。Swoole 的协程本质是:单个 OS 线程内,由 C 层维护的多个用户栈 + 寄存器上下文 + 状态机。
验证方式很简单:
- 启动 Hyperf 后执行
ps -T -p $(pgrep -f "hyperf.php start"),你会发现 Worker 进程只有 1 个 LWP(轻量级进程,即 OS 线程) - 但通过
Co::stats()查看,coroutine_num可能高达数千甚至上万 - 所有协程共享同一个 PHP 全局变量(如
$GLOBALS),但每个协程有独立的Context(通过Co::getContext()或Hyperf\Context\Context::get()访问)
为什么不能在协程里用 sleep() 或 file_get_contents()?
因为它们是同步阻塞函数,会挂起整个 OS 线程,而不是仅挂起当前协程。比如:
-
sleep(1)→ OS 线程休眠 1 秒,所有协程都停摆 -
file_get_contents("http://api.com")→ 底层走 curl 或 stream_socket,阻塞等待响应,同样冻结线程 - 而
Co::sleep(1)或$client->get()(Hyperf\HttpClient)→ 仅当前协程让出,其他协程继续运行
Hyperf 的组件(如 Hyperf\Database、Hyperf\Redis)全部基于 Swoole 协程 MySQL/Redis 客户端封装,自动将阻塞调用转为协程 yield。但如果你手动 new PDO 或 redis extension 实例,就立刻掉出协程上下文,引发并发数据错乱或连接泄漏。
Context::set() 和 Co::getContext() 的分工差异
Hyperf 的 Context 类是对 Swoole 原生 Co::getContext() 的增强封装,核心区别在于:
-
Co::getContext()返回原始数组,键名随意,无类型约束,且跨协程不隔离(若直接操作原生 context,可能污染其他协程) -
Hyperf\Context\Context::set($key, $value)默认使用协程 ID 做 namespacing,确保set和get严格绑定当前协程 - 框架内部大量使用
Context::set('request', $request),后续任何地方调用Context::get('request')都能安全取到本请求对象,哪怕在 Service 层、中间件、AOP 切面里
真正容易被忽略的是:一旦你在协程中 fork 出子协程(如用 Parallel 或 WaitGroup),父协程的 Context 不会自动继承到子协程。必须显式传递或重新绑定,否则子协程里 Context::get('request') 会返回 null。










