0

PHP 8.1 — Fibers (green threads)

Advanced5 min read·php-09-012

Concept

Fibers (PHP 8.1) are "green threads" — a form of cooperative multitasking where execution can be suspended and resumed manually. Unlike OS threads, Fibers don't run concurrently; only one Fiber runs at a time. They enable asynchronous programming patterns without callbacks or generators when combined with an event loop.

How Fibers work: A Fiber has its own call stack (unlike generators, which piggyback on the caller's stack). $fiber->start(...$args) begins execution. Inside the Fiber, Fiber::suspend($value) pauses execution and yields a value to the caller. Back in the caller, $fiber->resume($value) passes a value back into the Fiber and continues it. The Fiber maintains its own stack — you can call regular functions, nest function calls, and suspend() from deep inside.

Difference from generators: Generators must yield from the function itself — you can't yield from a function called by the generator. Fibers can Fiber::suspend() from any depth in the call stack. This makes Fibers suitable for implementing async I/O abstractions where suspending happens deep in a call chain.

Not concurrent: Fibers are cooperative, not preemptive. An event loop (like ReactPHP or Revolt) schedules Fibers by resuming them when I/O is ready. Without an event loop, Fibers are purely a flow-control mechanism.

Practical use: Most developers won't use Fibers directly — they're the foundation on which async PHP libraries (ReactPHP, Revolt, Amp 3) build their async/await abstractions. Understanding them explains how await-like syntax works in PHP.

Code Example

php
<?php
declare(strict_types=1);

// Basic Fiber — produce values, receive values
$fiber = new Fiber(function(): void {
    echo "Fiber start\n";
    $value = Fiber::suspend('first yield'); // suspend, yield 'first yield' to caller
    echo "Fiber resumed with: $value\n";
    $value2 = Fiber::suspend('second yield');
    echo "Fiber resumed again with: $value2\n";
    echo "Fiber done\n";
});

$yielded1 = $fiber->start();                    // runs until first suspend
echo "Caller got: $yielded1\n";                 // "first yield"
$yielded2 = $fiber->resume('hello');             // resumes, runs until second suspend
echo "Caller got: $yielded2\n";                 // "second yield"
$fiber->resume('world');                         // resumes, runs to completion

// Output:
// Fiber start
// Caller got: first yield
// Fiber resumed with: hello
// Caller got: second yield
// Fiber resumed again with: world
// Fiber done

// Simulating concurrent I/O (conceptual — not actual parallelism)
function simulateAsyncRequest(string $url, float $delay): string
{
    echo "Starting request to $url\n";
    Fiber::suspend(); // "waiting for I/O" — suspend to let other fibers run
    // After resume, I/O is complete
    return "Response from $url";
}

$fibers = [
    new Fiber(fn() => simulateAsyncRequest('https://api1.com', 0.1)),
    new Fiber(fn() => simulateAsyncRequest('https://api2.com', 0.2)),
];

// Simple round-robin scheduler (normally handled by an event loop library)
foreach ($fibers as $f) $f->start();     // start all
foreach ($fibers as $f) {
    if (!$f->isTerminated()) {
        $result = $f->resume();          // resume each once I/O "completes"
        echo "Got: $result\n";
    }
}

// Check fiber state
$f = new Fiber(fn() => Fiber::suspend());
var_dump($f->isStarted());    // false
$f->start();
var_dump($f->isSuspended());  // true
$f->resume();
var_dump($f->isTerminated()); // true