PHP协程依赖Swoole运行时,必须在Swoole\Coroutine\run()或Swoole\Http\Server的onRequest中执行;混用同步函数、误用FPM环境或未启动事件循环均导致协程失效。

PHP 本身没有可直接用于高并发场景的协程能力,所谓“学 PHP 协程”,本质是学 Swoole 的协程运行时和配套生态——不装 Swoole、不进 CLI 模式、不改 I/O 调用方式,所有协程代码都会退化成同步阻塞,甚至直接报错。
co::sleep() 报错 “must be called in the coroutine” 怎么办
这是最典型的环境误判:你写了 co::sleep(1),但脚本没跑在 Swoole 的协程上下文中。
-
Swoole\Coroutine\run()是 CLI 场景下唯一可靠入口,必须包裹全部协程逻辑,不能只包co::sleep()一行 - Apache/Nginx + PHP-FPM 组合下,
co::sleep()永远会报这个错——FPM 没事件循环,Swoole 根本无法初始化协程调度器 - Web 服务必须用
Swoole\Http\Server启动,onRequest回调里才默认处于协程环境,此时可直接调用co::sleep()或Co\MySQL->query() - 别在 Laravel/ThinkPHP 的控制器里写
go(),框架运行在 FPM 上,不是协程容器
go() 启动后什么都没输出,协程是不是没执行
常见于忘记启动事件循环:go() 只是注册任务,不触发调度;没 Swoole\Event::wait() 或 Swoole\Coroutine\run(),协程根本不会跑。
- CLI 脚本中,
go()后必须跟Swoole\Event::wait()(Swoole <4.8)或直接用Swoole\Coroutine\run()包裹整个逻辑 - 错误写法:
go(function () { echo "hello"; }); echo "done";→ 输出done后脚本立即退出,hello永远不会打印 - 正确写法:
Swoole\Coroutine\run(function () { go(function () { echo "hello"; }); }); -
go()和Swoole\Coroutine\create()完全等价,选一个习惯用法即可,无需纠结区别
为什么用了 co::sleep() 还是卡住整个进程
因为混用了 usleep()、sleep()、file_get_contents() 等原生同步函数——它们不会让出控制权,而是直接阻塞当前线程,所有协程一起停摆。
立即学习“PHP免费学习笔记(深入)”;
-
co::sleep(1)是协程让出,调度器可切走;usleep(1000000)是系统级休眠,整个进程冻结 - 启用
Swoole\Runtime::enableCoroutine()后,部分函数(如curl_exec)会被自动 Hook,但mysqli、PDO、fopen("http://")仍不支持,必须显式替换 - HTTP 请求必须用
Swoole\Coroutine\Http\Client,每个协程内独立 new 实例,不可复用 - MySQL 必须用
Co\MySQL,Redis 必须用Co\Redis;PDO 或 phpredis 一调就卡死
协程间怎么传数据?$_SESSION 或 global 可以吗
不可以。协程变量天然隔离,$_SESSION、global $x、静态属性在多个协程间共享会导致竞态,且值可能被覆盖或错乱。
- 要用
Co::getContext()获取当前协程私有上下文数组,例如:Co::getContext()['trace_id'] = $id; -
defer()只在当前协程退出时触发,适合清理资源(如关闭$client),但它不跨协程,也不保证超时后一定执行 - 传参必须用
use显式捕获,引用传递(&$results)才能让多个协程写入同一数组 - 连接池由
Co\MySQL等客户端内部管理,不用手写单例或全局连接对象
真正难的不是写 go(),而是识别哪些调用是隐式阻塞的——比如你以为 json_decode() 很快,但如果它前面连着一个没设 timeout 的 $client->get(),整个协程就卡在那儿了。协程性能收益,全系于 I/O 是否真正 yield,而不是语法上有没有 yield 关键字。



















