0

Coroutine vs generator vs fiber — what each term means precisely

Intermediate5 min read·eng-12-013
interviewcompare

Concept

Coroutine vs Generator vs Fiber — three related but distinct concepts for cooperative concurrency in PHP.

Generator (PHP 5.5+): A function that can yield values. It pauses at each yield and resumes when the caller calls next() or send(). The "coroutine" communication is one-way (generator produces, caller consumes) unless you use send(). Generators have a single call stack — they can only yield from the generator function itself, not from a function it calls.

Coroutine (computer science term): A function that can pause and resume at arbitrary points, with bidirectional communication. PHP generators are a LIMITED coroutine implementation. They ARE coroutines when you use send(), but the limitation is that yield must appear directly in the generator function (can't pause from a nested call).

Fiber (PHP 8.1+): A full coroutine. Has its OWN call stack (unlike generators). Can be suspended from anywhere in its call stack — from a function that the Fiber calls, not just directly in the Fiber function. Fiber::suspend($value) pauses, $fiber->resume($value) continues.

Key differences:

GeneratorFiber
Own call stackNoYes
Can suspend from nested callNoYes
Communicationyield / send()suspend() / resume()
PHP version5.5+8.1+

ReactPHP / Amp: Async PHP frameworks that use generators/fibers as coroutines to simulate non-blocking I/O in PHP's single-threaded model.

Laravel / Swoole: Octane + Swoole uses fibers for concurrent request handling.

Code Example

php
<?php
// GENERATOR as coroutine — bidirectional with send()
function accumulator(): \Generator
{
    $total = 0;
    while (true) {
        $value = yield $total; // yield current total, receive next value via send()
        if ($value === null) break;
        $total += $value;
    }
}

$acc = accumulator();
$acc->current();    // init: run to first yield, total = 0
$acc->send(10);     // total = 10
$acc->send(20);     // total = 30
echo $acc->current(); // 30

// Generator LIMITATION — can't yield from a nested call
function helper(): void
{
    // yield 'from helper'; // FATAL ERROR — yield only works in generator functions
}

function generatorFn(): \Generator
{
    yield 1;
    helper();  // helper runs synchronously — can't suspend
    yield 2;
}

// FIBER — full coroutine with its own call stack
function deepHelper(): void
{
    echo "In deep helper\n";
    \Fiber::suspend('from deep helper'); // can suspend from nested call!
    echo "Deep helper resumed\n";
}

$fiber = new \Fiber(function(): void {
    echo "Fiber started\n";
    deepHelper();              // suspend happens HERE (inside a nested call)
    echo "Fiber completed\n";
});

$value = $fiber->start();     // 'from deep helper' — fiber suspended from inside deepHelper!
echo "Main got: {$value}\n"; // 'from deep helper'
$fiber->resume();              // resumes deepHelper, then the fiber
// Output: Fiber started, In deep helper, Main got: from deep helper, Deep helper resumed, Fiber completed

// PRACTICAL: Fiber in async context (simplified)
class AsyncHttp
{
    public static function get(string $url): string
    {
        // In a real async framework: initiate non-blocking request, then suspend
        \Fiber::suspend(['type' => 'http_request', 'url' => $url]);
        // When fiber is resumed, the HTTP response is the resume value
        return \Fiber::getCurrent()->getReturn();
    }
}

// Laravel/Swoole use fibers internally for concurrent request handling
// You don't typically create fibers manually in Laravel apps