PHP 8.1 Fiber 协程实战:5步构建高并发异步任务处理器

PHP 8.1 正式引入了 Fiber(纤程),这是 PHP 并发编程史上最重要的特性之一。不同于传统的同步阻塞模式,Fiber 允许你在同一个进程中实现协作式多任务切换,极大提升 I/O 密集型场景的处理效率。本文将带你从零开始,用 5 个关键步骤构建一个真实可运行的异步任务处理器,并分享几个实战踩坑经验。
一、什么是 Fiber?和 Generator 有什么区别?
在深入实战之前,先理解 Fiber 的核心概念。Fiber 是 PHP 8.1 新增的一种轻量级协程,允许函数在执行过程中主动暂停,并在稍后恢复执行。
Generator:只能从 Generator 内部 yield,调用方无法向其注入值后继续执行
Fiber:可以在任意嵌套的调用栈中暂停,支持双向数据传递,更接近真正的协程
Fiber:拥有独立的调用栈,暂停时保存完整的执行上下文
$fiber = new Fiber(function (): void {
$value = Fiber::suspend("fiber started");
echo "Fiber received: " . $value;
});
$value = $fiber->start();
echo "Main received: " . $value;
$fiber->resume("hello from main");二、实战场景:批量 HTTP 请求的并发处理
假设你需要批量调用第三方 API 获取数据,串行请求 100 个接口可能需要 10 秒,而并发处理可以压缩到 1-2 秒。我们用 Fiber + amphp 来实现这个场景。
先安装依赖:
composer require amphp/amp:^3.0 amphp/http-client:^5.0
然后创建 batch_request.php:
<?php
require "vendor/autoload.php";
use AmpFuture;
use AmpHttpClientHttpClientBuilder;
use AmpHttpClientRequest;
use function Ampasync;
use function AmpFutureawaitAll;
$client = HttpClientBuilder::buildDefault();
$urls = array_map(
fn($i) => "https://jsonplaceholder.typicode.com/todos/{$i}",
range(1, 20)
);
$futures = array_map(function (string $url) use ($client): Future {
return async(function () use ($url, $client) {
$response = $client->request(new Request($url));
$body = $response->getBody()->buffer();
return json_decode($body, true);
});
}, $urls);
[$errors, $results] = awaitAll($futures);
echo "成功: " . count($results) . " 条";三、手写一个简易 Fiber 调度器(深入理解原理)
使用框架固然方便,但理解底层原理才能真正掌握 Fiber。下面手写一个简易调度器:
<?php
class FiberScheduler {
private array $fibers = [];
public function add(Fiber $fiber): void { $this->fibers[] = $fiber; }
public function run(): void {
foreach ($this->fibers as $fiber) {
if (!$fiber->isStarted()) $fiber->start();
}
while (true) {
$allDone = true;
foreach ($this->fibers as $fiber) {
if ($fiber->isSuspended()) { $allDone = false; $fiber->resume(); }
if (!$fiber->isTerminated()) $allDone = false;
}
if ($allDone) break;
}
}
}
$scheduler = new FiberScheduler();
$scheduler->add(new Fiber(function (): void {
echo "[Task A] 开始
"; Fiber::suspend(); echo "[Task A] 完成
";
}));
$scheduler->add(new Fiber(function (): void {
echo "[Task B] 开始
"; Fiber::suspend(); echo "[Task B] 完成
";
}));
$scheduler->run();四、实战踩坑记录
坑 1:PDO 不支持异步查询
PDO 是同步阻塞的,在 Fiber 中调用 PDO 查询会阻塞整个事件循环。解决方案是使用 amphp/mysql 或 amphp/postgres 等支持异步的数据库驱动。
坑 2:Fiber 中抛出异常的处理
$fiber = new Fiber(function (): void {
Fiber::suspend();
throw new RuntimeException("Fiber 内部错误");
});
$fiber->start();
try {
$fiber->resume(); // 这里会抛出 RuntimeException
} catch (RuntimeException $e) {
echo "捕获到错误: " . $e->getMessage();
}坑 3:不要在 Fiber 外部调用 Fiber::suspend()
在 Fiber 上下文外部调用 Fiber::suspend() 会抛出 FiberError,使用前务必用 Fiber::getCurrent() !== null 检查上下文。
五、性能对比:Fiber vs 串行 vs 多进程
实测对比,场景:并发请求 50 个需要 200ms 响应时间的 HTTP 接口:
串行模式:50 × 200ms = 约 10 秒
Fiber 并发(单进程):约 200-300ms(提升约 40 倍)
多进程(pcntl_fork):约 400-500ms(进程创建和销毁有额外开销)
多线程(parallel 扩展):约 300-400ms(线程同步开销)
Fiber 在 I/O 密集型场景中的性能优势非常明显,且无需额外的进程/线程管理开销。建议在爬虫、批量 API 调用、并发数据库查询等场景中优先尝试,配合 amphp 生态系统,可以在不引入多进程复杂度的前提下获得接近多线程的并发性能。
发布评论
热门评论区: