HardТеория6 min

Fibers PHP 8.1+

Fiber class, suspend/resume, cooperative multitasking

Что такое Fiber?

Fiber — это легковесный поток выполнения с возможностью приостановки и возобновления. Fiber позволяет реализовать кооперативную многозадачность (cooperative multitasking) в PHP.

Обычная функция:    start ──────────────────────► return
Fiber:              start ──► suspend ──► resume ──► suspend ──► resume ──► return
                              ↓ value     ↑ value    ↓ value     ↑ value
                           (main code)              (main code)
<?php
declare(strict_types=1);

// Basic Fiber example
$fiber = new Fiber(function (): string {
    $value = Fiber::suspend('first');   // Pause, send 'first' to caller
    echo "Received: {$value}\n";        // 'hello'

    $value = Fiber::suspend('second');  // Pause again, send 'second'
    echo "Received: {$value}\n";        // 'world'

    return 'done';                      // Final return value
});

// Start the fiber
$result = $fiber->start();
echo "Got: {$result}\n";               // 'first'

// Resume with a value
$result = $fiber->resume('hello');
echo "Got: {$result}\n";               // 'second'

// Resume again
$result = $fiber->resume('world');
// Output: "Received: hello", "Received: world"

echo $fiber->getReturn();              // 'done'

Запомни: Fiber::suspend() приостанавливает fiber и возвращает значение в вызывающий код. $fiber->resume($value) возобновляет fiber, передавая значение в Fiber::suspend(). Это двусторонний канал коммуникации.

API класса Fiber

<?php
declare(strict_types=1);

$fiber = new Fiber(function (string $initial): string {
    echo "Started with: {$initial}\n";
    $value = Fiber::suspend('paused');
    return "finished with: {$value}";
});

// Fiber lifecycle methods
$fiber->start('init');        // Start fiber, pass initial argument
$fiber->resume('data');       // Resume suspended fiber

// State checking
$fiber->isStarted();          // bool — has start() been called?
$fiber->isRunning();          // bool — currently executing?
$fiber->isSuspended();        // bool — paused at suspend()?
$fiber->isTerminated();       // bool — completed or threw exception?

// Get return value (only after termination)
$fiber->getReturn();          // mixed — return value (throws if not terminated)

// Throw exception into fiber
$fiber = new Fiber(function (): void {
    try {
        Fiber::suspend();
    } catch (RuntimeException $e) {
        echo "Caught in fiber: {$e->getMessage()}\n";
    }
});

$fiber->start();
$fiber->throw(new RuntimeException('Error from outside'));
Метод Описание
start(mixed ...$args) Запускает fiber с аргументами
resume(mixed $value) Возобновляет приостановленный fiber
throw(Throwable $e) Бросает исключение в точку suspend
isStarted() Был ли fiber запущен
isRunning() Выполняется ли сейчас
isSuspended() Приостановлен ли
isTerminated() Завершён ли
getReturn() Возвращаемое значение
Fiber::suspend(mixed $value) Статический — приостановка изнутри

Ловушка экзамена: Fiber::suspend() — это СТАТИЧЕСКИЙ метод, вызывается изнутри fiber. $fiber->resume() — это метод экземпляра, вызывается из основного кода. Нельзя вызвать resume() на fiber, который не приостановлен.

Состояния Fiber

<?php
declare(strict_types=1);

$fiber = new Fiber(function (): void {
    Fiber::suspend();
});

// State transitions:
// Created → start() → Running → suspend() → Suspended → resume() → Running → return → Terminated
//                                          → throw()  → Running

var_dump($fiber->isStarted());     // false
var_dump($fiber->isSuspended());   // false
var_dump($fiber->isTerminated());  // false

$fiber->start();

var_dump($fiber->isStarted());     // true
var_dump($fiber->isSuspended());   // true
var_dump($fiber->isTerminated());  // false

$fiber->resume();

var_dump($fiber->isStarted());     // true
var_dump($fiber->isSuspended());   // false
var_dump($fiber->isTerminated());  // true

FiberError

<?php
declare(strict_types=1);

// FiberError is thrown for invalid fiber operations
$fiber = new Fiber(function (): void {
    Fiber::suspend();
});

// Cannot resume fiber that hasn't started
try {
    $fiber->resume();  // FiberError
} catch (FiberError $e) {
    echo $e->getMessage();  // "Cannot resume a fiber that is not suspended"
}

$fiber->start();
$fiber->resume();  // Fiber terminates

// Cannot resume terminated fiber
try {
    $fiber->resume();  // FiberError
} catch (FiberError $e) {
    echo $e->getMessage();  // "Cannot resume a terminated fiber"
}

// Cannot get return from non-terminated fiber
$fiber2 = new Fiber(function (): string {
    Fiber::suspend();
    return 'result';
});
$fiber2->start();

try {
    $fiber2->getReturn();  // FiberError
} catch (FiberError $e) {
    echo $e->getMessage();  // "Cannot get return value of a fiber that hasn't terminated"
}

// Cannot call suspend() outside of a fiber
try {
    Fiber::suspend();  // FiberError
} catch (FiberError $e) {
    echo $e->getMessage();  // "Cannot suspend outside of a fiber"
}

Ловушка экзамена: Fiber::suspend() вне fiber выбросит FiberError. getReturn() до завершения fiber — тоже FiberError. Нельзя resume() на не-приостановленный fiber и нельзя start() повторно.

Практические паттерны

Генератор значений (аналог Generator)

<?php
declare(strict_types=1);

// Fiber-based value producer
$counter = new Fiber(function (): void {
    $i = 0;
    while (true) {
        Fiber::suspend($i++);
    }
});

$counter->start();              // returns 0
echo $counter->resume();       // 1
echo $counter->resume();       // 2
echo $counter->resume();       // 3

Scheduler (планировщик задач)

<?php
declare(strict_types=1);

class Scheduler
{
    /** @var SplQueue<Fiber> */
    private SplQueue $queue;

    public function __construct()
    {
        $this->queue = new SplQueue();
    }

    public function schedule(Fiber $fiber): void
    {
        $this->queue->enqueue($fiber);
    }

    public function run(): void
    {
        while (!$this->queue->isEmpty()) {
            $fiber = $this->queue->dequeue();

            if (!$fiber->isStarted()) {
                $fiber->start();
            } elseif ($fiber->isSuspended()) {
                $fiber->resume();
            }

            // Re-schedule if not terminated
            if (!$fiber->isTerminated()) {
                $this->queue->enqueue($fiber);
            }
        }
    }
}

// Usage
$scheduler = new Scheduler();

$scheduler->schedule(new Fiber(function (): void {
    echo "Task A - step 1\n";
    Fiber::suspend();
    echo "Task A - step 2\n";
}));

$scheduler->schedule(new Fiber(function (): void {
    echo "Task B - step 1\n";
    Fiber::suspend();
    echo "Task B - step 2\n";
}));

$scheduler->run();
// Output:
// Task A - step 1
// Task B - step 1
// Task A - step 2
// Task B - step 2

Async I/O simulation

<?php
declare(strict_types=1);

// Simulating non-blocking I/O with Fibers
function asyncFetch(string $url): Fiber
{
    return new Fiber(function () use ($url): string {
        // In real code, this would start a non-blocking HTTP request
        echo "Starting fetch: {$url}\n";
        Fiber::suspend();  // Yield control while "waiting"

        // "Response received"
        return "Response from {$url}";
    });
}

$fibers = [
    asyncFetch('https://api.example.com/users'),
    asyncFetch('https://api.example.com/posts'),
    asyncFetch('https://api.example.com/comments'),
];

// Start all
foreach ($fibers as $fiber) {
    $fiber->start();
}

// Resume all (simulate responses arriving)
$results = [];
foreach ($fibers as $fiber) {
    $fiber->resume();
    $results[] = $fiber->getReturn();
}

print_r($results);

Fiber vs Generator

Аспект Generator Fiber
Создание function* / yield new Fiber(callable)
Приостановка yield $value Fiber::suspend($value)
Возобновление $gen->send($val) $fiber->resume($val)
Глубина вызовов Только в самой функции В любом месте стека
Итерация Реализует Iterator Нет
Двусторонний обмен Да (yield/send) Да (suspend/resume)
Основное назначение Ленивые последовательности Кооперативная многозадачность
<?php
declare(strict_types=1);

// KEY difference: Fiber::suspend() works from ANY depth

function innerFunction(): void
{
    // This works in Fiber!
    Fiber::suspend('from deep inside');
}

function middleFunction(): void
{
    innerFunction();
}

$fiber = new Fiber(function (): void {
    middleFunction();  // suspend() called from 3 levels deep — OK!
});

$result = $fiber->start();
echo $result;  // 'from deep inside'

// Generators CANNOT do this!
// yield only works directly in the generator function

Запомни: Главное преимущество Fiber перед Generator — Fiber::suspend() можно вызвать на ЛЮБОЙ глубине стека вызовов. yield работает только непосредственно в функции-генераторе. Это делает Fiber идеальным для библиотек async I/O.

Fiber в реальных проектах

<?php
declare(strict_types=1);

// Fibers are the foundation for async frameworks:
// - ReactPHP (event loop)
// - Amp v3 (uses Fibers natively)
// - Revolt (event loop for PHP)
// - AMPHP (concurrent HTTP requests)

// Example concept: non-blocking delay
class EventLoop
{
    /** @var array<array{fiber: Fiber, readyAt: float}> */
    private array $timers = [];

    public function delay(float $seconds): void
    {
        $fiber = Fiber::this();  // Get current fiber (PHP 8.2+: fictional)
        // In reality, the event loop handles this internally
        $this->timers[] = [
            'fiber' => $fiber,
            'readyAt' => microtime(true) + $seconds,
        ];
        Fiber::suspend();
    }

    public function run(): void
    {
        while (!empty($this->timers)) {
            $now = microtime(true);
            foreach ($this->timers as $key => $timer) {
                if ($timer['readyAt'] <= $now) {
                    unset($this->timers[$key]);
                    $timer['fiber']->resume();
                }
            }
            usleep(1000);  // Prevent busy-waiting
        }
    }
}

// Note: In production, use Amp v3 or ReactPHP
// which provide proper event loop implementations

Обработка ошибок в Fiber

<?php
declare(strict_types=1);

// Exception propagation
$fiber = new Fiber(function (): void {
    throw new RuntimeException('Error inside fiber');
});

try {
    $fiber->start();  // Exception propagates to caller
} catch (RuntimeException $e) {
    echo "Caught: {$e->getMessage()}\n";  // "Error inside fiber"
}

// Injecting exceptions via throw()
$fiber = new Fiber(function (): string {
    try {
        $value = Fiber::suspend('waiting');
        return "Got: {$value}";
    } catch (RuntimeException $e) {
        return "Error handled: {$e->getMessage()}";
    }
});

$fiber->start();
$fiber->throw(new RuntimeException('Timeout'));
echo $fiber->getReturn();  // "Error handled: Timeout"

Проверь себя

Что вернёт `Fiber::suspend($value)` при возобновлении?

Можно ли вызвать `Fiber::suspend()` из вложенной функции?

Главное отличие Fiber от Generator?

Что произойдёт при вызове `resume()` на завершённый Fiber?

Как приостановить выполнение Fiber изнутри?