Fiber 状态管理
概述
Fiber 在其生命周期中会经历多种状态转换。理解这些状态及其转换规则对于正确使用 Fiber 至关重要。本章将详细讲解 Fiber 的状态枚举、状态转换图、生命周期管理,以及在每个状态下哪些操作是允许的。
关键概念
Fiber 的状态决定了你可以对其执行的操作。在错误的状态下调用 Fiber 方法会抛出 FiberError。
基础概念
Fiber 状态枚举
PHP 定义了以下 Fiber 状态常量:
Fiber::STATUS_INIT— 纤程已创建但尚未启动Fiber::STATUS_RUNNING— 纤程正在执行Fiber::STATUS_SUSPENDED— 纤程已暂停Fiber::STATUS_FINISHED— 纤程已正常结束Fiber::STATUS_DEAD— 纤程因异常终止
状态转换规则
每个状态只允许特定的操作,执行其他操作会导致 FiberError。
Fiber::getCurrent()
Fiber::getCurrent() 返回当前正在执行的 Fiber 实例。如果在 Fiber 外部调用,返回 null。
语法与代码
获取 Fiber 状态
<?php
declare(strict_types=1);
$fiber = new Fiber(function (): void {
echo "内部状态: " . Fiber::getCurrent()->status() . "\n";
Fiber::suspend();
echo "恢复后继续\n";
});
echo "初始状态: " . $fiber->status() . "\n"; // INIT (1)
$fiber->start();
// 内部状态: RUNNING (2)
echo "暂停状态: " . $fiber->status() . "\n"; // SUSPENDED (3)
$fiber->resume();
echo "结束状态: " . $fiber->status() . "\n"; // FINISHED (4)各状态的允许操作
<?php
declare(strict_types=1);
function testStateTransitions(): void
{
$fiber = new Fiber(function (): void {
Fiber::suspend('paused');
return 'done';
});
// --- INIT 状态 ---
assert($fiber->status() === \Fiber::STATUS_INIT);
// start() ✓ throw() ✗ resume() ✗
$result = $fiber->start();
// --- SUSPENDED 状态 ---
assert($fiber->status() === \Fiber::STATUS_SUSPENDED);
// start() ✗ throw() ✓ resume() ✓
$result = $fiber->resume();
// --- FINISHED 状态 ---
assert($fiber->status() === \Fiber::STATUS_FINISHED);
// start() ✗ throw() ✗ resume() ✗
}
testStateTransitions();Fiber 异常终止(DEAD 状态)
<?php
declare(strict_types=1);
$fiber = new Fiber(function (): void {
Fiber::suspend('alive');
throw new \RuntimeException('fatal error');
});
$fiber->start();
try {
$fiber->resume();
} catch (\RuntimeException $e) {
echo "异常: {$e->getMessage()}\n";
}
echo "状态: " . $fiber->status() . "\n"; // FINISHED (4) — 即使异常也标记为 FINISHED状态说明
PHP 8.1 中 Fiber::STATUS_DEAD 在实际使用中很少出现——因异常终止的 Fiber 状态实际上仍为 FINISHED。DEAD 状态可能在内部实现中用于标记其他终止场景。
Fiber::getCurrent() 在不同上下文中
<?php
declare(strict_types=1);
echo "主线程: " . var_export(\Fiber::getCurrent(), true) . "\n";
// 主线程: NULL
$fiber = new Fiber(function (): void {
$current = \Fiber::getCurrent();
echo "Fiber 内部: " . var_export($current !== null, true) . "\n";
Fiber::suspend();
});
$fiber->start();
// Fiber 内部: true详细说明
完整状态转换图
new Fiber()
│
▼
┌──────────┐
│ INIT │
└────┬─────┘
│ start()
▼
┌──────────┐
│ RUNNING │◄───────┐
└──┬──┬──┬──┘ │
│ │ │ │
│ │ └── suspend() ──►┌───────────┐
│ │ │ SUSPENDED │
│ │ └─────┬─────┘
│ │ resume()/throw()
│ │ │
│ └── 异常 ──────────────►│
│ │
▼ │
┌───────────┐◄─────────────────────┘
│ FINISHED │
└───────────┘状态与方法的对照表
| 方法 | INIT | RUNNING | SUSPENDED | FINISHED |
|---|---|---|---|---|
start() | 允许 | FiberError | FiberError | FiberError |
resume() | FiberError | FiberError | 允许 | FiberError |
throw() | FiberError | FiberError | 允许 | FiberError |
suspend() | - | 允许(内部) | - | - |
status() | 允许 | 允许 | 允许 | 允许 |
getReturn() | FiberError | FiberError | FiberError | 允许 |
getReturn() 方法
<?php
declare(strict_types=1);
$fiber = new Fiber(function (): string {
Fiber::suspend();
return 'final result';
});
$fiber->start();
$fiber->resume();
echo $fiber->getReturn() . "\n"; // final result
// 对于 void 类型的 Fiber
$fiber2 = new Fiber(function (): void {
Fiber::suspend();
});
$fiber2->start();
$fiber2->resume();
echo var_export($fiber2->getReturn(), true); // NULL实战示例
实战:状态监控器
<?php
declare(strict_types=1);
class FiberMonitor
{
/** @var array<string, string> */
private array $history = [];
public function track(string $name, \Fiber $fiber): void
{
$status = match ($fiber->status()) {
\Fiber::STATUS_INIT => 'INIT',
\Fiber::STATUS_RUNNING => 'RUNNING',
\Fiber::STATUS_SUSPENDED => 'SUSPENDED',
\Fiber::STATUS_FINISHED => 'FINISHED',
default => 'UNKNOWN',
};
$this->history[$name][] = [
'status' => $status,
'time' => microtime(true),
];
}
public function getHistory(string $name): array
{
return $this->history[$name] ?? [];
}
}
$monitor = new FiberMonitor();
$fiber = new Fiber(function (): void {
Fiber::suspend();
Fiber::suspend();
});
$name = 'worker-1';
$monitor->track($name, $fiber); // INIT
$fiber->start();
$monitor->track($name, $fiber); // SUSPENDED
$fiber->resume();
$monitor->track($name, $fiber); // SUSPENDED
$fiber->resume();
$monitor->track($name, $fiber); // FINISHED
print_r($monitor->getHistory($name));实战:状态驱动的任务系统
<?php
declare(strict_types=1);
class TaskSystem
{
/** @var array<int, \Fiber> */
private array $tasks = [];
private int $taskId = 0;
public function createTask(callable $callback): int
{
$id = $this->taskId++;
$this->tasks[$id] = new \Fiber($callback);
return $id;
}
public function tick(): void
{
foreach ($this->tasks as $id => $fiber) {
$status = $fiber->status();
if ($status === \Fiber::STATUS_INIT) {
$fiber->start();
} elseif ($status === \Fiber::STATUS_SUSPENDED) {
$fiber->resume();
}
if ($fiber->status() === \Fiber::STATUS_FINISHED) {
unset($this->tasks[$id]);
}
}
}
public function hasPendingTasks(): bool
{
return !empty($this->tasks);
}
}
$system = new TaskSystem();
$system->createTask(function (): void {
echo "任务 1: 开始\n";
Fiber::suspend();
echo "任务 1: 继续\n";
});
$system->createTask(function (): void {
echo "任务 2: 开始\n";
Fiber::suspend();
Fiber::suspend();
echo "任务 2: 结束\n";
});
while ($system->hasPendingTasks()) {
$system->tick();
}注意事项
不要在 RUNNING 状态操作 Fiber
在 Fiber 的回调函数内部,不能对自身调用 start()、resume() 或 throw()。这会导致死锁或 FiberError。
状态检查的重要性
在调用 Fiber 的方法之前,最好先检查其状态,避免抛出 FiberError。
<?php
declare(strict_types=1);
function safeResume(\Fiber $fiber, mixed $value = null): void
{
if ($fiber->status() === \Fiber::STATUS_SUSPENDED) {
$fiber->resume($value);
}
}getReturn 在未完成时调用
<?php
declare(strict_types=1);
$fiber = new Fiber(function (): void {
Fiber::suspend();
});
$fiber->start();
try {
$fiber->getReturn();
} catch (\Throwable $e) {
echo "异常: " . $e->getMessage() . "\n";
// The fiber has not returned
}最佳实践
- 调用前检查状态:在操作 Fiber 之前使用
status()检查当前状态。 - 使用 match 表达式处理状态:PHP 8.0+ 的
match表达式非常适合处理 Fiber 状态。 - 封装安全的操作方法:创建封装类来处理 Fiber 状态检查和方法调用。
- 记录状态变化:在调试时记录 Fiber 的状态变化有助于排查问题。
<?php
declare(strict_types=1);
class SafeFiber
{
public function __construct(
private readonly \Fiber $fiber
) {}
public function start(mixed ...$args): mixed
{
if ($this->fiber->status() !== \Fiber::STATUS_INIT) {
throw new \RuntimeException('Fiber 不是 INIT 状态');
}
return $this->fiber->start(...$args);
}
public function resume(mixed $value = null): mixed
{
if ($this->fiber->status() !== \Fiber::STATUS_SUSPENDED) {
throw new \RuntimeException('Fiber 不是 SUSPENDED 状态');
}
return $this->fiber->resume($value);
}
public function isRunning(): bool
{
return $this->fiber->status() === \Fiber::STATUS_SUSPENDED
|| $this->fiber->status() === \Fiber::STATUS_INIT;
}
}状态转换的详细分析
完整状态转换表
以下表格展示了每个方法在不同状态下的行为:
| 当前状态 | start() | resume() | throw() |
|---|---|---|---|
| 未启动 | 创建新栈,执行回调 | FiberError | FiberError |
| 挂起 | FiberError | 恢复执行,传入值 | 恢复执行,抛出异常 |
| 已结束 | FiberError | FiberError | FiberError |
| 已终止 | FiberError | FiberError | FiberError |
Fiber 的错误处理
在 Fiber 执行过程中,如果发生未捕获的异常,Fiber 会进入终止(terminated)状态,异常会被转换为 FiberError 抛出给调用者。
<?php
declare(strict_types=1);
$fiber = new \Fiber(function (): void {
throw new \RuntimeException("Fiber 内部错误");
});
try {
$fiber->start();
} catch (\FiberError $e) {
echo "捕获 FiberError: " . $e->getMessage() . "\n";
$previous = $e->getPrevious();
if ($previous !== null) {
echo "原始异常: " . $previous->getMessage() . "\n";
}
}
echo "Fiber 状态: " . match (true) {
$fiber->isTerminated() => 'terminated',
$fiber->isSuspended() => 'suspended',
$fiber->isRunning() => 'running',
default => 'unknown',
} . "\n";FiberError vs 内部异常
FiberError 是 Fiber 状态操作错误的包装器,原始异常存储在 getPrevious() 中。务必检查 getPrevious() 获取真正的错误信息。
Fiber 栈的内存管理
每个 Fiber 创建时都会分配独立的调用栈。栈的大小可以通过 fiber.stack_size PHP INI 配置调整。
<?php
declare(strict_types=1);
// 查看当前 Fiber 栈大小配置
echo "Fiber 栈大小: " . ini_get('fiber.stack_size') . " bytes\n";
// 默认通常是 8MB 或由系统决定栈大小调整
对于需要深度递归的 Fiber,可以适当增大栈大小。但需要注意,每个 Fiber 都会消耗对应大小的虚拟内存。
实战:Fiber 状态监控器
<?php
declare(strict_types=1);
class FiberMonitor
{
/** @var array<string, \Fiber> */
private array $fibers = [];
/** @var array<string, string> */
private array $states = [];
public function create(string $name, callable $callback): \Fiber
{
$fiber = new \Fiber(function () use ($callback, $name): void {
$this->states[$name] = 'running';
$callback();
$this->states[$name] = 'finished';
});
$this->fibers[$name] = $fiber;
$this->states[$name] = 'created';
return $fiber;
}
public function getStates(): array
{
foreach ($this->fibers as $name => $fiber) {
if (isset($this->states[$name]) && $this->states[$name] !== 'finished') {
$this->states[$name] = match (true) {
$fiber->isRunning() => 'running',
$fiber->isSuspended() => 'suspended',
$fiber->isTerminated() => 'terminated',
default => 'unknown',
};
}
}
return $this->states;
}
public function summary(): string
{
$lines = [];
foreach ($this->getStates() as $name => $state) {
$lines[] = "Fiber '{$name}': {$state}";
}
return implode("\n", $lines);
}
}
$monitor = new FiberMonitor();
$fiber = $monitor->create('worker', function (): void {
echo "工作中...\n";
\Fiber::suspend();
echo "恢复工作...\n";
});
$monitor->create('helper', function (): void {
echo "辅助任务...\n";
});
echo $monitor->summary() . "\n";
$fiber->start();
echo $monitor->summary() . "\n";
$fiber->resume();
echo $monitor->summary() . "\n";常见误区与 FAQ
Fiber 可以重启吗?
不能。一个 Fiber 一旦结束(正常结束或异常终止),就不能再次启动。必须创建新的 Fiber 实例。
如何判断 Fiber 是否可以 resume?
使用 $fiber->isSuspended() 方法。只有处于挂起状态的 Fiber 才可以被 resume() 或 throw()。
多个 Fiber 之间的通信
Fiber 之间通过 resume($value) 传递值进行通信。suspend() 的返回值就是 resume() 的参数,反之亦然。
<?php
declare(strict_types=1);
$producer = new \Fiber(function (): void {
$data = ['task1', 'task2', 'task3'];
foreach ($data as $item) {
$consumerResponse = \Fiber::suspend($item);
echo "生产者收到: {$consumerResponse}\n";
}
});
$consumer = new \Fiber(function () use ($producer): void {
while ($producer->isSuspended()) {
$item = $producer->resume();
echo "消费者处理: {$item}\n";
}
});
$consumer->start();