Fiber 创建与启动
概述
创建和启动 Fiber 是使用纤程的第一步。本章将详细讲解如何通过 new Fiber() 创建纤程实例、使用 Fiber::start() 启动执行、通过 Fiber::suspend() 暂停纤程、以及通过 Fiber::resume() 恢复纤程的执行。掌握这些核心操作是使用 Fiber 进行异步编程的基础。
核心方法
Fiber::start() → Fiber::suspend() → Fiber::resume() 是 Fiber 最基本的使用模式。
基础概念
new Fiber($callback)
创建一个新的 Fiber 实例。回调函数是纤程的入口点,当纤程启动时会执行这个回调。
Fiber::start()
启动纤程的执行。可以向回调函数传递初始参数。纤程开始执行直到遇到 Fiber::suspend() 或回调结束。
Fiber::suspend()
在纤程内部调用,暂停纤程的执行。可以传递一个值给调用者(resume 的返回值)。
Fiber::resume()
从主线程恢复暂停的纤程。可以传递一个值给纤程(suspend 的返回值)。
语法与代码
创建并启动 Fiber
php
<?php
declare(strict_types=1);
// 创建 Fiber
$fiber = new Fiber(function (): void {
echo "Fiber 开始执行\n";
Fiber::suspend();
echo "Fiber 继续执行\n";
});
echo "1. Fiber 状态: " . $fiber->status() . "\n"; // INIT (1)
// 启动 Fiber
$fiber->start();
echo "2. Fiber 状态: " . $fiber->status() . "\n"; // SUSPENDED (3)
// 恢复 Fiber
$fiber->resume();
echo "3. Fiber 状态: " . $fiber->status() . "\n"; // FINISHED (4)Fiber::start() 传参
php
<?php
declare(strict_types=1);
$fiber = new Fiber(function (string $name, int $age): string {
$message = "姓名: {$name}, 年龄: {$age}";
Fiber::suspend($message);
return "处理完成";
});
$result = $fiber->start('Alice', 30);
echo $result . "\n"; // 姓名: Alice, 年龄: 30
$finalResult = $fiber->resume();
echo $finalResult . "\n"; // 处理完成Fiber::suspend() 传值
php
<?php
declare(strict_types=1);
$fiber = new Fiber(function (): void {
for ($i = 1; $i <= 3; $i++) {
$received = Fiber::suspend("第{$i}次暂停");
echo "收到: {$received}\n";
}
});
// 首次 start
$result = $fiber->start();
echo "暂停返回: {$result}\n"; // 第1次暂停
// 后续 resume
$result = $fiber->resume('数据A');
echo "暂停返回: {$result}\n"; // 第2次暂停
// 收到: 数据A
$result = $fiber->resume('数据B');
echo "暂停返回: {$result}\n"; // 第3次暂停
// 收到: 数据B
$fiber->resume('数据C');
// 收到: 数据C
// Fiber 自然结束Fiber::resume() 传值
php
<?php
declare(strict_types=1);
$fiber = new Fiber(function (): int {
echo "Fiber 等待输入...\n";
$input = Fiber::suspend();
$result = (int) $input * 2;
Fiber::suspend($result);
return $result;
});
$fiber->start();
// Fiber 等待输入...
$doubled = $fiber->resume(21);
echo "计算结果: {$doubled}\n"; // 计算结果: 42
$fiber->resume();Fiber 返回值
php
<?php
declare(strict_types=1);
$fiber = new Fiber(function (): array {
$items = [];
while (count($items) < 3) {
$items[] = Fiber::suspend(count($items) . ' items');
}
return $items;
});
$result = $fiber->start(); // 0 items
$result = $fiber->resume('a'); // 1 items
$result = $fiber->resume('b'); // 2 items
$final = $fiber->resume('c'); // Fiber 结束
print_r($final);
// Array ( [0] => a [1] => b [2] => c )详细说明
启动流程详解
1. $fiber = new Fiber($callback)
→ 状态: INIT
→ 回调尚未执行
2. $fiber->start()
→ 状态: RUNNING
→ 回调开始执行
→ 遇到 Fiber::suspend($value)
→ 状态: SUSPENDED
→ start() 返回 $value
3. $fiber->resume($sendValue)
→ 状态: RUNNING
→ Fiber::suspend() 的返回值为 $sendValue
→ 回调继续执行
→ 遇到 Fiber::suspend($value2)
→ 状态: SUSPENDED
→ resume() 返回 $value2
4. 回调执行完毕(return 或结束)
→ 状态: FINISHED
→ resume() 返回回调的 return 值错误状态下的方法调用
php
<?php
declare(strict_types=1);
$fiber = new Fiber(function (): void {
Fiber::suspend();
});
// 在 INIT 状态下调用 resume → FiberError
try {
$fiber->resume();
} catch (\FiberError $e) {
echo "错误: " . $e->getMessage() . "\n";
// Cannot resume a fiber that has not been started
}
// 在 INIT 状态下调用 throw → FiberError
try {
$fiber->throw(new \RuntimeException('test'));
} catch (\FiberError $e) {
echo "错误: " . $e->getMessage() . "\n";
// Cannot throw into a fiber that has not been started
}
// 正确流程
$fiber->start(); // 正常启动
$fiber->resume(); // 正常恢复(Fiber 结束)
// $fiber->resume(); // FiberError: Cannot resume a fiber that has finished多个 Fiber 的交互
php
<?php
declare(strict_types=1);
$fiberA = new Fiber(function (): void {
echo "Fiber A: 步骤 1\n";
Fiber::suspend();
echo "Fiber A: 步骤 2\n";
});
$fiberB = new Fiber(function (): void {
echo "Fiber B: 步骤 1\n";
Fiber::suspend();
echo "Fiber B: 步骤 2\n";
});
// 交叉执行
$fiberA->start(); // Fiber A: 步骤 1
$fiberB->start(); // Fiber B: 步骤 1
$fiberA->resume(); // Fiber A: 步骤 2
$fiberB->resume(); // Fiber B: 步骤 2实战示例
实战:生产者-消费者模式
php
<?php
declare(strict_types=1);
class Channel
{
/** @var \SplQueue<mixed> */
private \SplQueue $queue;
private ?\Fiber $waitingConsumer = null;
private ?\Fiber $waitingProducer = null;
public function __construct()
{
$this->queue = new \SplQueue();
}
public function send(mixed $value): void
{
if ($this->waitingConsumer !== null) {
$consumer = $this->waitingConsumer;
$this->waitingConsumer = null;
$consumer->resume($value);
} else {
$this->queue->enqueue($value);
Fiber::suspend();
}
}
public function receive(): mixed
{
if (!$this->queue->isEmpty()) {
return $this->queue->dequeue();
}
$this->waitingConsumer = \Fiber::getCurrent();
return Fiber::suspend();
}
}
$channel = new Channel();
// 消费者
$consumer = new Fiber(function () use ($channel): void {
for ($i = 0; $i < 3; $i++) {
$value = $channel->receive();
echo "消费: {$value}\n";
}
});
// 生产者
$producer = new Fiber(function () use ($channel): void {
$items = ['apple', 'banana', 'cherry'];
foreach ($items as $item) {
echo "生产: {$item}\n";
$channel->send($item);
}
});
$consumer->start();
$producer->start();
$consumer->resume();
$producer->resume();
$consumer->resume();
$producer->resume();实战:任务调度器
php
<?php
declare(strict_types=1);
class SimpleScheduler
{
/** @var \SplQueue<\Fiber> */
private \SplQueue $fibers;
public function __construct()
{
$this->fibers = new \SplQueue();
}
public function spawn(callable $callback): void
{
$this->fibers->enqueue(new \Fiber($callback));
}
public function run(): void
{
while (!$this->fibers->isEmpty()) {
$fiber = $this->fibers->dequeue();
if ($fiber->status() === \Fiber::STATUS_INIT) {
$fiber->start();
} elseif ($fiber->status() === \Fiber::STATUS_SUSPENDED) {
$fiber->resume();
}
if ($fiber->status() === \Fiber::STATUS_SUSPENDED) {
$this->fibers->enqueue($fiber);
}
}
}
}
$scheduler = new SimpleScheduler();
$scheduler->spawn(function (): void {
for ($i = 0; $i < 3; $i++) {
echo "任务 A-{$i}\n";
Fiber::suspend();
}
});
$scheduler->spawn(function (): void {
for ($i = 0; $i < 3; $i++) {
echo "任务 B-{$i}\n";
Fiber::suspend();
}
});
$scheduler->run();注意事项
不能在非 Fiber 上下文中调用 suspend
php
<?php
declare(strict_types=1);
// Fiber::suspend() 只能在 Fiber 内部调用
// Fiber::suspend(); // FiberError: Cannot suspend outside of a fiberFiber 的异常传播
如果在 Fiber 中抛出未捕获的异常,该异常会传播到调用 start()/resume() 的上下文中。
php
<?php
declare(strict_types=1);
$fiber = new Fiber(function (): void {
Fiber::suspend();
throw new \RuntimeException('Fiber 内部异常');
});
$fiber->start();
try {
$fiber->resume(); // 异常传播到此处
} catch (\RuntimeException $e) {
echo "捕获异常: {$e->getMessage()}\n";
}最佳实践
- 封装 Fiber 创建:将 Fiber 创建和启动封装为更高层的抽象,降低使用复杂度。
- 使用 Fiber::getCurrent():在 Fiber 内部通过
Fiber::getCurrent()获取当前 Fiber 实例。 - 异常处理:确保在 Fiber 内部或外部正确处理异常。
- 使用 try/finally:在 Fiber 回调中使用 try/finally 确保资源释放。
php
<?php
declare(strict_types=1);
function spawnFiber(callable $callback): \Fiber
{
return new \Fiber(function () use ($callback): void {
try {
$callback();
} catch (\Throwable $e) {
echo "Fiber 异常: {$e->getMessage()}\n";
}
});
}