PHP 8.1 Fibers: Cooperative Concurrency Explained
PHP 8.1 introduced Fibers, a powerful feature that brings low-level, lightweight cooperative concurrency to the language. This article explores how Fibers work, how they differ from traditional asynchronous programming patterns, and how they allow developers to pause and resume code execution without the overhead of operating system threads or the complexity of promise-based callback chains.
Understanding Cooperative Concurrency
In preemptive concurrency (like multi-threading), the operating system scheduler decides when to pause one thread and start another. In cooperative concurrency, the running code itself voluntarily yields control back to the scheduler when it is waiting for an operation to complete, such as an I/O network call or file read.
Fibers are the language construct that enables this cooperative behavior in PHP. They are essentially interruptible functions that can be paused at any point in their execution and resumed later from the exact same state.
How Fibers Work in PHP 8.1
A Fiber is an object wrapper around a block of code (a callable) that
maintains its own call stack. PHP 8.1 introduced the Fiber
class, which provides a simple API to control this execution flow:
Fiber::start(): Starts the execution of the fiber.Fiber::suspend(): Pauses the execution of the current fiber and returns control (and optionally a value) back to the calling code.Fiber::resume(): Restarts a suspended fiber from the exact point it was paused, optionally passing a value back into the fiber.Fiber::isSuspended()/Fiber::isTerminated(): Helper methods to check the current state of the fiber.
Code Example: Basic Fiber Control Flow
Here is a simple example showing how execution control jumps back and forth between the main program and a Fiber:
$fiber = new Fiber(function (): void {
echo "Fiber started\n";
// Suspend the fiber and send a value to the caller
$valuePassedToMain = Fiber::suspend('Value from Fiber');
echo "Fiber resumed with: " . $valuePassedToMain . "\n";
echo "Fiber ended\n";
});
// 1. Start the fiber. It runs until it hits Fiber::suspend()
$outputFromFiber = $fiber->start();
echo "Main program received: " . $outputFromFiber . "\n";
// 2. Resume the fiber and pass a value back into it
$fiber->resume('Value from Main');Output:
Fiber started
Main program received: Value from Fiber
Fiber resumed with: Value from Main
Fiber ended
Why Fibers are “Lightweight”
Traditional multi-threading requires the operating system to allocate memory stacks for each thread, causing significant CPU and memory overhead during context switching.
Fibers are lightweight because they run within a single PHP process and share the same OS thread. The PHP runtime manages the switching of call stacks internally. Because there is no OS-level context switching, thousands of Fibers can run concurrently with negligible memory and CPU overhead.
The Key Benefit: Eliminating “Function Coloring”
Before PHP 8.1, asynchronous PHP frameworks (like ReactPHP or Amp)
relied on Promises and Generators (yield). This led to
“function coloring”—the problem where an asynchronous function can only
be called by another asynchronous function, forcing developers to chain
.then() promises or use generators throughout the entire
call stack.
Fibers solve this problem. Because a Fiber can be suspended from anywhere within the call stack—even deep inside nested function calls—you do not need to change the signatures of your functions. Standard, synchronous-looking PHP code can be paused and resumed transparently.
The Role of Fibers in the PHP Ecosystem
It is important to note that Fibers themselves do not provide an event loop or perform asynchronous I/O operations out of the box. They are low-level building blocks.
To achieve true non-blocking I/O, Fibers must be used in conjunction with an event loop library (such as Revolt, Amp v3, or ReactPHP). These frameworks use Fibers under the hood to pause execution when a socket is waiting for data, and resume it automatically when the data becomes available, allowing developers to write clean, synchronous-looking asynchronous code.