Fibers and Async PHP

Reviewed & published by Brayan K

By the end of this lesson you'll understand PHP 8.1 Fibers — how to start, suspend, resume, and read a Fiber's getReturn value — what cooperative multitasking really means, how async runtimes like ReactPHP, Amp, and Swoole use Fibers to do non-blocking I/O without callbacks, and how generators (yield) compare.

Part of the free PHP course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

What You'll Learn in This Lesson

1️⃣ PHP Is Synchronous by Default

Out of the box, PHP runs one statement after another and blocks on I/O — a call like file_get_contents() stops the whole script until it returns. For a single web request that's simple and fast. But if one process must make many slow calls, it spends almost all its time waiting, doing nothing useful.

<?php
// PHP is SYNCHRONOUS and BLOCKING by default. Each line waits for the
// one before it. A slow call like this freezes everything after it.

function slowFetch(string $name): string
{
    // imagine this is a network call that takes a second
    return "data:" . $name;
}

echo slowFetch("a") . "\n";   // runs, then waits, then prints
echo slowFetch("b") . "\n";   // only starts AFTER "a" is fully done
echo "done\n";
?>

Notice slowFetch("b") can't even begin until slowFetch("a") has completely finished. Fibers give us a way to overlap that waiting.

2️⃣ Your First Fiber: start & suspend

A Fiber (PHP 8.1) is a block of code you can pause and resume, and it keeps its own call stack while paused. You wrap a callback in new Fiber(...), then start() it. Inside, Fiber::suspend($value) pauses and hands $value back to the caller; $fiber->resume($value) continues it, and that argument becomes the result of the suspend() call.

<?php
// A Fiber is a block of code you can PAUSE and RESUME.
// Introduced in PHP 8.1. It keeps its own call stack.

$fiber = new Fiber(function (): void {
    echo "1) fiber started\n";

    // Pause HERE and hand "halfway" back to the outside code.
    $resumeValue = Fiber::suspend("halfway");

    // Execution continues here when someone calls ->resume(...)
    echo "3) fiber resumed with: " . $resumeValue . "\n";
});

// start() runs the fiber until the first suspend(). It returns the
// value that suspend() handed out.
$suspended = $fiber->start();
echo "2) outside got: " . $suspended . "\n";

// resume() continues the fiber; the argument becomes the result of
// the suspend() call inside it.
$fiber->resume("go!");

echo "4) all done\n";
?>

Read the numbered lines in order: control ping-pongs between the Fiber and the outside code. The Fiber runs to its suspend(), hands a value out, and waits — exactly one of them is ever running at a time.

3️⃣ Suspending Repeatedly & getReturn

A Fiber can suspend many times, then finish by returning a value. You drive it in a loop with resume() until isTerminated() is true, and read the final value with $fiber->getReturn().

<?php
// A Fiber can suspend several times and then RETURN a final value.
// You read that value afterwards with getReturn().

$counter = new Fiber(function (): string {
    $total = 0;
    foreach (["x", "y", "z"] as $step) {
        $total++;
        // pause and report which step we're on
        Fiber::suspend("processing " . $step);
    }
    return "finished " . $total . " steps";   // becomes getReturn()
});

$status = $counter->start();            // runs to first suspend
while (!$counter->isTerminated()) {     // keep resuming until it ends
    echo $status . "\n";
    $status = $counter->resume();       // resume; capture next suspend value
}

echo $counter->getReturn() . "\n";     // the function's return value
?>

This pause-report-resume loop is the heart of a scheduler: each pass lets the Fiber make a little progress, then yields control so other work can run before it's resumed.

4️⃣ How Async Runtimes Use Fibers

Cooperative multitasking means each task runs until it voluntarily yields by suspending — nothing interrupts it, so there are no data races. Async runtimes (ReactPHP, Amp, Swoole) build an event loop on top of this: when your code awaits a network call, the runtime suspends the Fiber, lets the loop run other Fibers, and resumes yours when the data arrives. The result is async code that reads like ordinary blocking code — no callback pyramids.

<?php
// HOW ASYNC RUNTIMES USE FIBERS (simplified sketch of ReactPHP/Amp/Swoole).
// An event loop runs many fibers, suspending each one while it "waits"
// for I/O and resuming it when the result is ready. No callbacks needed.

$tasks = [];
foreach (["users", "orders"] as $name) {
    $tasks[] = new Fiber(function () use ($name): void {
        echo "start request: " . $name . "\n";
        // In a real runtime, suspend() yields control while the network
        // call is in flight; the loop resumes us when data arrives.
        Fiber::suspend();
        echo "got response: " . $name . "\n";
    });
}

// The "event loop": start every task, then resume them as I/O completes.
foreach ($tasks as $t) { $t->start(); }     // all requests kicked off
foreach ($tasks as $t) { $t->resume(); }    // all responses delivered
echo "loop finished\n";
?>

Both requests start before either response arrives — that's the overlap. In a real runtime the resume() calls are triggered by I/O completions, not a simple loop, but the shape is identical. This is also what powers long-lived WebSocket servers.

5️⃣ Generators (yield) vs Fibers

You may have met yield before — generators also pause and resume. The key difference: a generator can only yield from its own top-level body and is shaped like an iterator. A Fiber can suspend() from deep inside any nested function it calls, several frames down — which is precisely what makes transparent async possible, since the suspend point usually lives inside a library's networking code, not in your function.

<?php
// GENERATORS (yield) also pause and resume — but only the generator
// function ITSELF can yield, and it is shaped like an iterator.
// A Fiber can suspend from deep inside any nested call; yield cannot.

function steps(): Generator
{
    yield "one";
    yield "two";
    yield "three";
}

foreach (steps() as $value) {   // each loop pass resumes the generator
    echo $value . "\n";
}
?>

Generators were PHP's older, more limited tool for this. Fibers, added in 8.1, removed the restriction — which is why modern async frameworks moved onto them.

Now you try the two core skills — the start/suspend handshake, and reading a return value.

Fill in each ___ using the 👉 hint, then run it and check it against the Output panel.

<?php
// 🎯 YOUR TURN — start a Fiber, catch its suspended value, then resume it.
// Fill in each blank marked ___ , then run it.

$fiber = new Fiber(function (): void {
    echo "inside: before pause\n";
    Fiber::___("paused");      // 👉 the method that pauses a fiber
    echo "inside: after pause\n";
});

$value = $fiber->___();        // 👉 the method that begins the fiber
echo "outside got: " . $value . "\n";
$fiber->resume();              // continue the fiber to the end

// ✅ Expected output:
//    inside: before pause
//    outside got: paused
//    inside: after pause
?>

One more. Let the Fiber finish, then read the value it returned.

<?php
// 🎯 YOUR TURN — read a Fiber's final return value.

$fiber = new Fiber(function (): int {
    Fiber::suspend();          // pause once
    return 42;                 // this is the final return value
});

$fiber->start();               // runs to the suspend
$fiber->resume();              // runs to the return (terminates)

// Read the value the fiber returned:
echo "result: " . $fiber->___() . "\n";   // 👉 the method that reads the return

// ✅ Expected output:
//    result: 42
?>

Common Errors (and the fix)

Pro Tips

📋 Quick Reference — Fibers

APIWhat It Does
new Fiber($callback)Create a Fiber wrapping a callback (PHP 8.1+)
$fiber->start(...$args)Run until the first suspend; returns its value
Fiber::suspend($value)Pause from inside; hand $value to the caller
$fiber->resume($value)Continue; $value becomes the suspend() result
$fiber->isTerminated()True once the callback has returned
$fiber->getReturn()The callback's final return value
yield (generator)Pause a generator's own body only — weaker

Mini-Challenge: A Cooperative Scheduler

No code is filled in this time — just a brief and an outline. Build three Fibers, start them all, then resume them all, and watch the interleaving. This is the essence of how an event loop overlaps work without threads.

<?php
// 🎯 MINI-CHALLENGE: Build a tiny cooperative scheduler with Fibers.
// No code is filled in — work from the steps below, then run it.
//
// 1. Create THREE fibers. Each one echoes "task N: step 1", then calls
//    Fiber::suspend(), then echoes "task N: step 2".
// 2. Store them in an array.
// 3. Loop over the array and ->start() every fiber (you'll see all the
//    "step 1" lines first).
// 4. Loop again and ->resume() every fiber (now all the "step 2" lines).
// 5. This interleaving — start all, then resume all — is exactly how an
//    event loop overlaps work without threads.
//
// ✅ Expected output (order):
//    task 1: step 1 / task 2: step 1 / task 3: step 1
//    task 1: step 2 / task 2: step 2 / task 3: step 2

// your code here
?>

🎉 Lesson Complete!

Practice quiz

In which PHP version were Fibers introduced?

  • PHP 8.1
  • PHP 7.4
  • PHP 8.0
  • PHP 5.6

Answer: PHP 8.1. Fibers landed in PHP 8.1, giving the language a low-level primitive for pausing and resuming a block of code.

What is a Fiber, in one sentence?

  • A new kind of array
  • A full-featured OS thread that runs truly in parallel
  • A block of code you can pause and later resume, keeping its own call stack
  • A database connection pool

Answer: A block of code you can pause and later resume, keeping its own call stack. A Fiber is a unit of execution with its own stack that can suspend itself and be resumed later from the same point. It is cooperative, not parallel.

By default, how does PHP execute code?

  • Asynchronously with a built-in event loop
  • Synchronously, one statement after another, blocking on I/O
  • Randomly
  • In parallel across all CPU cores

Answer: Synchronously, one statement after another, blocking on I/O. PHP is synchronous and blocking by default: a call like file_get_contents waits until it finishes before the next line runs.

Which method starts a Fiber running?

  • $fiber->begin()
  • $fiber->go()
  • $fiber->run()
  • $fiber->start()

Answer: $fiber->start(). $fiber->start() begins execution; any arguments you pass become the arguments of the Fiber's callback. It runs until the first suspend or until it returns.

What does Fiber::suspend($value) do?

  • Pauses the current Fiber and hands $value back to whoever called start() or resume()
  • Throws an exception
  • Starts a new Fiber
  • Permanently stops the Fiber

Answer: Pauses the current Fiber and hands $value back to whoever called start() or resume(). Fiber::suspend() is called from inside the Fiber. It pauses execution and returns its argument to the outside code that resumed it.

What does $fiber->resume($value) do?

  • Restarts the Fiber from the beginning
  • Continues a suspended Fiber, and $value becomes the return value of the suspend() call inside it
  • Kills the Fiber
  • Returns the Fiber's final value

Answer: Continues a suspended Fiber, and $value becomes the return value of the suspend() call inside it. resume() continues a suspended Fiber from where it paused, and the value you pass becomes the result of the Fiber::suspend() call that paused it.

How do you read the value a Fiber returned after it finishes?

  • $fiber->output()
  • $fiber->result()
  • $fiber->value()
  • $fiber->getReturn()

Answer: $fiber->getReturn(). Once a Fiber's callback has returned (and the Fiber is terminated), $fiber->getReturn() gives you that final return value.

What style of multitasking do Fibers provide?

  • No multitasking at all
  • Preemptive multitasking, where PHP interrupts code at any time
  • Cooperative multitasking, where code yields control voluntarily by suspending
  • True parallelism across cores

Answer: Cooperative multitasking, where code yields control voluntarily by suspending. Fibers are cooperative: a Fiber keeps running until it chooses to suspend. Nothing interrupts it, so there are no data races from preemption.

How do event-loop runtimes like ReactPHP, Amp, and Swoole use Fibers?

  • They replace PHP with Node.js
  • They suspend a Fiber while an I/O operation is pending and resume it when the data is ready, so async code reads like sync code
  • They run each Fiber on a separate CPU core
  • They compile Fibers to machine code

Answer: They suspend a Fiber while an I/O operation is pending and resume it when the data is ready, so async code reads like sync code. Async runtimes drive an event loop. When a Fiber awaits I/O they suspend it, let the loop do other work, and resume it when the result arrives, removing callback nesting.

What is the main difference between a generator (yield) and a Fiber?

  • A generator can only suspend at its own top level and is iterator-shaped; a Fiber can suspend from deep inside any nested function call
  • Generators run in parallel, Fibers do not
  • There is no difference
  • Generators are faster than Fibers in every case

Answer: A generator can only suspend at its own top level and is iterator-shaped; a Fiber can suspend from deep inside any nested function call. yield only pauses the generator function itself. Fiber::suspend() can pause from anywhere in the call stack beneath the Fiber, which is what makes transparent async possible.

Continue this course

Frequently asked questions

Do Fibers make PHP multi-threaded or parallel?

No. Fibers are a concurrency primitive, not parallelism. Exactly one Fiber runs at any instant on a single thread, and a Fiber keeps running until it chooses to suspend. What Fibers buy you is the ability to overlap waiting: while one Fiber is suspended waiting for a network response, another can run. That is ideal for I/O-bound work (HTTP calls, database queries) but does not speed up CPU-bound work, which still needs separate processes or an extension like parallel.

What is the difference between a generator (yield) and a Fiber?

Both can pause and resume, but a generator can only yield from its own top-level body and is shaped like an iterator — you drive it by iterating. A Fiber is more powerful: Fiber::suspend() can be called from deep inside any function the Fiber calls, several layers down the call stack, and control still returns cleanly to the code that resumed the Fiber. That deep, transparent suspension is exactly what async runtimes need, because the suspend point is often buried inside a library's networking code rather than in the code you wrote.

If I never call a runtime, what do start, suspend, resume, and getReturn actually do?

They are the four moving parts of the Fiber API. $fiber->start() begins the Fiber and runs it until the first Fiber::suspend() (or until it returns); start() returns whatever suspend() handed out. Fiber::suspend($value), called from inside the Fiber, pauses it and passes $value back out. $fiber->resume($value) continues the suspended Fiber, and the $value you pass becomes the result of the suspend() call inside. Finally, once the Fiber's callback has returned, $fiber->getReturn() gives you that final return value.

Do I write raw Fiber code in real applications?

Rarely directly. Fibers are a low-level building block. In day-to-day code you use an async framework — ReactPHP, Amp, or Swoole — that runs an event loop and uses Fibers internally. Their value is that they let those frameworks turn callback-heavy, deeply nested async code into ordinary-looking sequential code: you write what reads like a normal blocking call, and under the hood the framework suspends your Fiber while the I/O is in flight and resumes it when the data arrives.

Why is PHP synchronous by default, and when does async actually help?

Traditional PHP runs one request per process and executes top to bottom, blocking on each I/O call until it returns. For a typical web request that is simple and perfectly fast. Async helps when a single process must do many independent I/O operations that spend most of their time waiting — for example a service that fans out twenty HTTP calls, or a long-lived WebSocket server handling thousands of connections. There, suspending Fibers while requests are in flight lets one process keep busy instead of idling on each blocking call.