PHP 8.1 的 Fiber 不是开箱即用的协程,需手动配合非阻塞 I/O 和事件轮询才能发挥优势;仅在已知可非阻塞等待资源(如异步 socket 连接、curl_multi 响应)时调用 suspend() 才有意义。

PHP 8.1 的 Fiber 不是开箱即用的协程,它本身不处理异步 I/O,也不自动挂起阻塞调用。高效使用的关键在于:只在明确可控的挂起点手动 Fiber::suspend(),且必须配合非阻塞资源轮询(如 stream_select、curl_multi_exec 或 ext-uv),否则和普通函数没区别,甚至更慢。
什么时候该用 Fiber::suspend(),而不是直接写同步逻辑
只有当你的代码中存在「已知可非阻塞等待」的资源时才值得挂起——比如你刚发起一个 stream_socket_client(..., STREAM_CLIENT_ASYNC_CONNECT),但还没连上;或刚提交一个 curl_multi_add_handle(),但响应还没回来。此时调用 Fiber::suspend() 是把控制权交还给调度器,让它去检查其他 Fiber 是否就绪。若只是读本地文件、算 MD5、查数组键,挂起毫无意义,反而增加上下文切换开销。
- ✅ 适用场景:HTTP 客户端等待响应、数据库连接握手、长轮询 socket 等待数据到达
- ❌ 误用场景:
fopen()后立刻Fiber::suspend()(文件 I/O 仍是阻塞的)、sleep(1)前挂起(没释放任何资源) - ⚠️ 注意:
file_get_contents('http://...')这类原生函数永远阻塞,Fiber对它完全无感,挂起也等不到唤醒信号
$fiber->resume() 和 $fiber->throw() 的触发时机必须由外部事件驱动
不能靠定时器或随机间隔调用 resume()。真正高效的调度器会监听所有 Fiber 关联的资源(如 socket 文件描述符),一旦 stream_select() 返回可读/可写,就立即恢复对应 Fiber。否则你只是用 Fiber 包了一层同步逻辑,还多了一次栈保存/恢复。
- 典型错误:在
while (true) { usleep(10000); $fiber->resume(); }中轮询——这等于用 Fiber 模拟线程,失去轻量优势 - 正确做法:把 socket 加入
$read_fds数组,stream_select($read_fds, ...)返回后,遍历找出哪些 socket 就绪,再对对应 Fiber 调用resume() - 异常传递要小心:
$fiber->throw(new Exception())会把异常抛到Fiber::suspend()调用点,不是整个 Fiber 函数入口,需确保挂起点外层有try/catch
别把 Fiber 当生成器用,二者生命周期和用途完全不同
Fiber 是一次性执行流,start() 启动后只能 resume() 一次(除非它自己再次 suspend());而 Generator 可多次 yield。混用会导致状态混乱,比如试图用 foreach 遍历一个 Fiber 实例,会直接报 Fatal error: Uncaught Error: Cannot traverse an instance of Fiber。
立即学习“PHP免费学习笔记(深入)”;
-
Fiber适合封装单次异步任务(如“发请求→等响应→解析”) -
Generator适合数据流处理(如逐行读大文件、分页 API 响应迭代) - 想组合多个异步步骤?用多个
Fiber+ 调度器协调,不要在一个 Fiber 里嵌套 yield + suspend
最常被忽略的一点:Fiber 的性能收益完全取决于你是否真的让出了 CPU 去干别的事。如果调度器逻辑臃肿、I/O 检测不及时、或者大量 Fiber 在等同一个慢资源,那 suspend/resume 只是在空转栈帧——这时候不如老老实实用 Swoole 或 ReactPHP 的成熟协程封装。



















