PHP协程高效运行必须满足三个硬条件:运行在Swoole/Workerman协程上下文里、所有IO操作走协程化驱动、避免任何同步阻塞调用;否则协程退化为单线程,因无调度器接管yield/Fiber挂起后的控制权。

PHP协程不是“写了yield就能并发”,真正在业务中高效运行,必须满足三个硬条件:运行在Swoole/Workerman协程上下文里、所有IO操作走协程化驱动、避免任何同步阻塞调用。
为什么yield和Fiber不能直接用于生产HTTP服务
PHP 8.1+ 的 Fiber 是底层调度原语,不带 IO 自动挂起能力;Generator + yield 只是协程形态的模拟,无法触发事件循环调度。你在FPM或CLI下写个 yield 函数,它照样会卡住整个进程——因为没有调度器接管挂起后的控制权。
- 只有
Swoole\Coroutine\run()、swoole_http_server、Workerman\Worker启动时才真正激活协程调度器 -
Fiber::suspend()不会自动等网络就绪,你得自己监听 socket 状态再 resume,成本远高于直接用Co\Http\Client - PHP 8.5 原生协程仍处于实验阶段,
async/await语法未被 Zend VM 完全支持,目前无稳定 runtime
必须替换掉这些阻塞函数,否则协程立刻退化为单线程
协程调度器靠“遇到IO就让出CPU”来实现并发,一旦碰到阻塞调用,整个事件循环就停摆。下面这些常见写法会让协程失效:
-
sleep(1)→ 改用Co::sleep(1)或usleep()(仅限 Swoole 4.8+ 协程环境) -
file_get_contents('http://...')→ 改用Co\Http\Client::get() -
PDO/mysqli→ 必须换成Swoole\Coroutine\MySQL或Workerman\Mysql\Connection -
curl_exec()→ 不行,哪怕加了CURLOPT_NOSIGNAL也不行,curl 是系统级阻塞
注意:echo、json_encode、数组遍历这些 CPU 操作没问题,协程不怕它们,怕的是等磁盘、网卡、数据库返回。
变量共享与作用域陷阱:传引用不是万能解
协程之间默认不共享局部变量,go(function () use ($data) { ... }) 是值拷贝;想改原始变量得显式传引用,但要注意生命周期错位:
-
use (&$results)可以让多个协程往同一个数组 push,但若主协程提前退出,$results可能被销毁而子协程还在写 - 全局静态变量、
static属性、global在协程间是共享的,极易引发竞态,比如并发更新static $counter++ - 推荐用
chan(Swoole 5.0+)或Workerman\Channel做安全通信,比裸引用更可控
协程泄漏和内存失控:没人告诉你栈大小会爆
每个协程默认分配 8KB 栈空间,看似很小,但 1000 个协程就是 8MB;如果协程里 hold 住大数组、未关闭的 Co\Redis 实例、或忘了 unset($bigData),内存只增不减。
- 用
swoole_coroutine_stats()查当前协程数和总内存占用,上线前必须埋点监控 - 设置
swoole_set_process_name('php-worker:coro-'.$cid)方便ps aux | grep coro排查长时协程 -
Co::defer()不是 try/finally,协程被max_exec_time杀掉时它大概率不执行,清理逻辑得放在try/finally里再包一层
最常被忽略的一点:协程函数里递归调用自身,没设深度限制,栈会一路涨到超限报 Fatal error: Stack overflow —— 这错误不会被 set_exception_handler 捕获,只能靠压测提前暴露。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











