/ PHP  Fiber  协程  PHP8.1  异步编程  高并发  amphp  性能优化 

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 生态系统,可以在不引入多进程复杂度的前提下获得接近多线程的并发性能。

发布评论

热门评论区: