Fiber 本身不提供自动并发,必须配合显式挂起(suspend)与轮询调度(resume)及非阻塞 I/O 才能实现真正并发;否则仍是串行执行。

Fiber 不是“调用就能并发”的函数,它必须配合显式挂起与轮询调度才能真正并发——否则只是换个写法的同步执行。
为什么直接 new Fiber(...)->start() 无法实现并发
常见错误是把多个耗时操作包进独立 Fiber,然后挨个 start(),以为这就“并发”了。实际现象是:总耗时 ≈ 所有任务耗时之和(串行),而非取最大值(并行)。
根本原因:Fiber::start() 启动后,控制权完全移交;若 Fiber 内部没有 Fiber::suspend(),它会一路执行到底才返回主流程——PHP 还是单线程,没人帮你切走。
- 哪怕你写了 10 个
new Fiber(...),不调度 = 全卡在第一个里 -
Fiber不是async/await,它不自动感知 I/O 就绪,也不集成事件循环 - 所有阻塞操作(如
fread、PDO::query)在 Fiber 内仍会阻塞整个进程
真正能并发的 Fiber 调度模式:轮询 + 非阻塞 I/O
要让多个 Fiber “交错推进”,必须由主流程主动控制节奏:启动全部,再逐个 resume() 一步,检查是否完成,没完就跳下一个——这就是轮询(round-robin)。
立即学习“PHP免费学习笔记(深入)”;
但前提是每个 Fiber 的 I/O 操作必须是非阻塞的,否则 resume() 一进去就卡死,轮询失效。
- 对文件流:用
stream_set_blocking($fp, false)+fread(),读不到数据时立即返回空字符串或false,此时才调用Fiber::suspend() - 对网络请求:不能直接
file_get_contents(),得用stream_socket_client()配非阻塞模式,自己解析 HTTP - 对数据库:原生
PDO不行,必须换Swoole\Coroutine\MySQL或Amp\MySQL\Connection
示例关键逻辑片段:
$fibers = array_map(function ($url) {
return new Fiber(function () use ($url) {
$fp = @fopen($url, 'r');
stream_set_blocking($fp, false);
while ($chunk = fread($fp, 1024)) {
echo "read from $url: " . strlen($chunk) . " bytes\n";
Fiber::suspend(); // 主动让出,等下次轮到它
}
fclose($fp);
});
}, $urls);
// 批量启动
foreach ($fibers as $fiber) $fiber->start();
// 轮询驱动
while (!empty($fibers)) {
foreach ($fibers as $i => $fiber) {
if ($fiber->isTerminated()) {
unset($fibers[$i]);
continue;
}
if ($fiber->isSuspended()) {
$fiber->resume(); // 只推一步
}
}
}
Fiber::resume() 和 Fiber::suspend() 的配对陷阱
这两个方法必须成对出现,且位置敏感。错一次,就会抛 FiberError 或静默失败。
-
Fiber::suspend()只能在当前 Fiber 内部调用,外部调用无效 -
Fiber::resume($value)必须在 Fiber 处于isSuspended()状态时调用,否则报错 -
start()的返回值 = 第一次suspend($value)的$value;后续resume($v)的$v是suspend()的返回值 - 别在
try/catch外层包resume()—— 若 Fiber 内部抛异常且未捕获,resume()会把它原样抛给主流程
别指望 Fiber 自动解决异步问题
Fiber 是调度机制,不是异步封装。它不改变底层 I/O 行为,也不替代事件循环。
如果你的需求是“发邮件不阻塞响应”,Fiber 本身做不到——Fiber::start() 仍会等 mail() 返回。必须把发信动作移到子进程(pcntl_fork)、消息队列(RabbitMQ)、或后台 Worker(Supervisor + CLI 脚本)中执行。
最容易被忽略的一点:Fiber 的栈是私有的,但全局状态(如 static 变量、$_SESSION、error_reporting 设置)仍是共享的。协程间没内存隔离,该加锁还得加锁,该重置还得重置。



















