PHP 8.1 Fibers
使用原生 Fibers 暂停和恢复执行。
PHP 8.1 Fibers 是 CoddyKit 上的免费 PHP Academy 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 PHP Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 PHP Academy 课程共包含 4 节课。
纤程能为您带来什么
PHP 8.1 新增了纤程:一种核心原语,可以暂停正在运行的调用栈,并从完全不同的位置稍后恢复它。纤程拥有完整且独立的调用栈,您可以在任意位置暂停它,之后继续执行,而无需展开调用栈。这正是库能够用看起来像同步的 await 替代承诺链所缺少的环节,也是 ReactPHP 和 Amp 现代 API 的基础。
您的第一个纤程
使用回调创建纤程,调用 start() 启动它,并让它自行调用 suspend()。控制权会在暂停点返回给调用者;resume() 会从该位置准确地继续执行。
<?php
$fiber = new Fiber(function (): void {
echo "A: inside fiber\n";
$received = Fiber::suspend('paused');
echo "C: resumed with '$received'\n";
});
$value = $fiber->start(); // prints A, returns 'paused'
echo "B: fiber suspended, got '$value'\n";
$fiber->resume('hello'); // prints C
echo "D: fiber done\n";四个核心方法
这个 API 很简洁:
$fiber->start(...$args)——开始执行;返回第一次暂停时的值。Fiber::suspend($value)——暂停当前纤程,将$value交给恢复执行的一方。$fiber->resume($value)——继续执行;$value会成为suspend()的返回值。$fiber->getReturn()——纤程终止后,获取回调的返回值。
值会在暂停边界的两个方向上流动。
双向传递值
纤程可以向外交出一个请求,并接收返回的响应——这正是事件循环将输入/输出结果送回暂停任务的核心机制。
<?php
$fiber = new Fiber(function (): int {
$a = Fiber::suspend('need first number');
$b = Fiber::suspend('need second number');
return $a + $b;
});
$fiber->start(); // suspends: 'need first number'
$fiber->resume(10); // a = 10, suspends: 'need second number'
$fiber->resume(32); // b = 32, fiber returns
echo $fiber->getReturn(), PHP_EOL; // 42纤程状态与自省
您可以查询纤程的生命周期。在已终止的纤程上调用 resume() 会抛出 FiberError,因此在构建调度器时,请使用这些检查进行防护。
<?php
$fiber = new Fiber(function () { Fiber::suspend(); });
var_dump($fiber->isStarted()); // false
$fiber->start();
var_dump($fiber->isStarted()); // true
var_dump($fiber->isSuspended()); // true
var_dump($fiber->isRunning()); // false (we're outside it)
$fiber->resume();
var_dump($fiber->isTerminated()); // true构建一个微型调度器
纤程加上队列,就构成了一个协作式调度器。每个任务都会暂停以让出控制权;调度器以轮流调度的方式运行,直到所有任务完成。这就是每个异步运行时内部的骨架。
<?php
$tasks = [];
foreach (['X', 'Y', 'Z'] as $name) {
$tasks[] = new Fiber(function () use ($name) {
for ($i = 1; $i <= 2; $i++) {
echo "$name step $i\n";
Fiber::suspend(); // give others a turn
}
});
}
foreach ($tasks as $t) { $t->start(); }
while ($tasks) {
foreach ($tasks as $k => $t) {
if ($t->isTerminated()) { unset($tasks[$k]); continue; }
$t->resume();
}
}跨越边界传递异常
throwInto()(在 API 中称为 throw(),写作 $fiber->throw())会在暂停点注入异常——这正是循环将失败的输入/输出操作报告回等待任务的方式。异常会在纤程暂停的位置准确地抛出。
<?php
$fiber = new Fiber(function () {
try {
Fiber::suspend();
} catch (RuntimeException $e) {
echo 'caught: ' . $e->getMessage() . PHP_EOL;
}
});
$fiber->start();
$fiber->throw(new RuntimeException('io failed')); // prints: caught: io failed纤程与生成器
生成器(yield)也可以暂停,但只能暂停顶层函数。要使用生成器从嵌套辅助函数中等待,您必须一路使用 yield from 向上贯穿整个调用栈,使每个函数都带上这种色彩。纤程可以从任意调用深度暂停整个调用栈,因此深层嵌套的调用可以调用 Fiber::suspend(),而无需让调用者知晓。这就是为什么 await 可以是普通函数调用,而不是 yield。
<?php
function deep(): string {
// No yield needed in the call chain — suspends the whole fiber
return Fiber::suspend('awaiting from deep');
}
$f = new Fiber(fn() => print(deep() . PHP_EOL));
$f->start();
$f->resume('value injected at depth');暂停限制
有一条严格规则:您只能在纤程内部调用 Fiber::suspend()。从主线程暂停,或跨越不支持纤程的 C 层回调暂停(例如某些旧式 usort 比较器和某些内部回调),都会抛出 FiberError: Cannot suspend outside of a fiber。库会确保所有用户代码都在由循环驱动的纤程内部运行,从而将这一限制隐藏起来。
<?php
try {
Fiber::suspend(); // main thread — illegal
} catch (FiberError $e) {
echo $e->getMessage(), PHP_EOL; // Cannot suspend outside of a fiber
}库如何使用纤程
您很少会在应用代码中直接编写原始纤程。Amp v3 和ReactPHP(通过纤程)等框架会对它们进行封装:await($promise) 内部调用 Fiber::suspend(),向事件循环注册延续,并在输入/输出操作完成后调用 resume() 恢复您的纤程。您的代码看起来像同步代码;运行时负责挂起和调度。
<?php
// Conceptual: what an await() helper does under the hood
function await(Promise $p): mixed {
$fiber = Fiber::getCurrent() ?? throw new Error('await() outside fiber');
$p->then(fn($v) => $fiber->resume($v),
fn($e) => $fiber->throw($e));
return Fiber::suspend(); // parked until the promise settles
}内存与生命周期注意事项
每个纤程都会分配自己的调用栈,因此创建数十万个纤程会产生内存开销(虽然比 OS 线程轻得多)。如果仍有对象引用某个永久暂停的纤程,它就永远不会被垃圾回收,这会成为长时间运行的服务器中的隐蔽泄漏。此外,如果纤程的回调抛出未捕获异常,异常会从 resume()/start() 调用中传播出来,因此调度器必须在异常捕获结构中包裹恢复操作。
<?php
$fiber = new Fiber(function () {
throw new RuntimeException('boom inside fiber');
});
try {
$fiber->start(); // exception surfaces here, at the resumer
} catch (RuntimeException $e) {
echo 'scheduler caught: ' . $e->getMessage() . PHP_EOL;
}快速检查
在异步场景中,纤程相较于生成器的关键优势是什么?
回顾
纤程是 PHP 的底层并发原语:
start()、Fiber::suspend()、resume()和getReturn()可以暂停并继续完整的调用栈,并在两个方向上传递值。- 状态方法(
isSuspended、isTerminated)和throw()让您能够构建协作式调度器并注入输入/输出错误。 - 与生成器不同,纤程可以从任意调用深度暂停,从而实现透明的
await()。 - 您只能在纤程内部暂停;Amp 和 ReactPHP 等框架会封装纤程,使您的异步代码读起来像同步代码。
常见问题解答
「PHP 8.1 Fibers」课时是免费的吗?
是的 — 「PHP 8.1 Fibers」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 PHP Academy 课程的其余内容,请升级到 CoddyKit PRO。 PHP Academy 课程共包含 4 节课。
「PHP 8.1 Fibers」这节课中我会学到什么?
使用原生 Fibers 暂停和恢复执行。 你通过在浏览器中直接运行的动手代码来练习 PHP Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 PHP Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 PHP Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。
「PHP 8.1 Fibers」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 PHP Academy 课中编写并运行代码吗?
能。每节 PHP Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。
此课程中的所有课时
- PHP 并发模型
- PHP 8.1 Fibers
- 使用 ReactPHP 的事件循环
- 使用 Swoole 构建高性能服务器