Understanding Fibers in PHP: A New Way to Handle Concurrency

itsimiro
7 min readAug 21, 2024

--

With the release of PHP 8.1, PHP introduced a new feature called Fibers, which offers a new approach to handling concurrency in PHP applications. While not as widely discussed as some other features, Fibers give developers a more granular control over code execution, enabling more efficient and non-blocking operations.

What Are Fibers?

Fibers in PHP are essentially a low-level concurrency management tool for collaborative multitasking. They allow code execution to be suspended and resumed at specific points without the need to create full-blown threads or processes. Simply put, fibers provide the ability to transfer control during the execution of a function and resume it from where it left off.

This is especially useful for writing non-blocking code. Unlike traditional blocking operations, where a function must terminate before the next function starts executing, fibers allow you to manage the flow of execution more efficiently, especially in I/O-related tasks such as database queries or HTTP requests.

How Do Fibers Work?

Fibers are implemented using the Fiber class in PHP. When you define a Fiber, you essentially create a function that can be started, paused, and resumed. Here's a basic example to illustrate how Fibers operate:

$fiber = new Fiber(function () {
echo "Start of fiber\n";
Fiber::suspend(); // Pause execution here
echo "Resume fiber\n";
});

echo "Before fiber start\n";
$fiber->start(); // Starts the fiber and runs until suspend
echo "After fiber start, before resume\n";
$fiber->resume(); // Resumes fiber execution from where it was suspended
echo "After fiber resume\n";

Output:

Before fiber start
Start of fiber
After fiber start, before resume
Resume fiber
After fiber resume

In this example:

  1. The start() method initiates Fiber and starts it until it reaches the Fiber::suspend() call, at which point execution is suspended.
  2. The resume() method continues execution from where it was suspended.

This ability to suspend and resume execution makes Fiber so powerful in managing tasks that would otherwise block the entire execution flow.

Why Use Fibers?

Fibers offer several advantages in PHP development, especially when it comes to tasks that require parallelism or non-blocking execution:

  • Non-blocking code: Fibers allow you to write code that does not block the main thread of execution. This is very important for I/O related tasks such as network requests or file operations where waiting for a response or data can cause delays.
  • Performance Improvements: By yielding control during waiting periods (e.g., waiting for a database query to complete), Fibers can help keep your application responsive and efficient, avoiding unnecessary bottlenecks.
  • Simplified Asynchronous Code: Traditionally, PHP has handled asynchronous tasks using callbacks or promises, which can lead to complex, hard-to-maintain code. Fibers provide a more straightforward, synchronous-like structure for managing asynchronous operations.

Practical Use Cases for Fibers

Although Fibers is a relatively new feature, it has several practical applications in PHP development:

  • Asynchronous I/O Operations: Fibers can be used to manage asynchronous tasks, such as fetching data from a remote API or reading from a file, without blocking other operations.
  • Concurrency in Web Servers: In custom PHP web servers or event-driven applications, Fibers can handle multiple requests concurrently, improving performance and responsiveness.
  • Cooperative Multitasking: Fibers enable cooperative multitasking, where different parts of an application yield control voluntarily, allowing other tasks to run. This is different from preemptive multitasking, where the system determines when to switch tasks.
function asyncOperation() {
echo "Starting async operation\n";
Fiber::suspend(); // Simulate waiting for I/O
echo "Async operation resumed\n";
}

$fiber = new Fiber('asyncOperation');

echo "Before starting fiber\n";
$fiber->start(); // Start and pause the fiber
echo "After fiber start, waiting...\n";
sleep(2); // Simulate some delay
$fiber->resume(); // Resume after the delay
echo "After fiber resumed\n";

Output:

Before starting fiber
Starting async operation
After fiber start, waiting...
Async operation resumed
After fiber resumed

This example demonstrates how you can simulate asynchronous behavior using Fibers, yielding control during an operation and resuming it after a delay.

Considerations and Limitations

While Fibers is a powerful addition to PHP, there are some important points to keep in mind:

  1. Not a replacement for multithreading: Fibers are not intended to replace multithreading or parallel processing. They are best suited for collaborative multitasking within a single thread.
  2. Additional complexity: While fibers simplify some types of asynchronous code, they introduce new complexities in managing fiber state and ensuring proper control flow.
  3. Version Compatibility: Fibers require PHP 8.1 or higher, so they are not available in older versions of PHP.

Example: Using Fibers to Mitigate I/O Blocking

Consider a scenario where you need to retrieve data from an external API and database and then process it. Traditionally, you can perform these operations sequentially, which can cause significant delays if any of them take time to complete.

Here’s what it might look like without using fibers:

function fetchDataFromApi() {
// Simulate an API request that takes 2 seconds
sleep(2);
return "API Data";
}

function fetchDataFromDatabase() {
// Simulate a database query that takes 3 seconds
sleep(3);
return "Database Data";
}

echo "Fetching data...\n";
$apiData = fetchDataFromApi();
$dbData = fetchDataFromDatabase();

echo "Processing data: $apiData and $dbData\n";

Output:

Fetching data...
(Waits 5 seconds)
Processing data: API Data and Database Data

In this scenario, the total time to retrieve and process data is 5 seconds because the operations are performed one after another. The code is blocked during API and database queries, making the application less responsive.

Now let’s see how we can use fibers to improve the situation by processing operations in parallel:

$fiberApi = new Fiber(function () {
// Simulate an API request that takes 2 seconds
sleep(2);
Fiber::suspend("API Data");
});

$fiberDb = new Fiber(function () {
// Simulate a database query that takes 3 seconds
sleep(3);
Fiber::suspend("Database Data");
});

echo "Fetching data...\n";

// Start both fibers
$fiberApi->start();
$fiberDb->start();

// Resume both fibers after they have finished their operations
$apiData = $fiberApi->resume();
$dbData = $fiberDb->resume();

echo "Processing data: $apiData and $dbData\n";

Output:

Fetching data...
(Waits 3 seconds)
Processing data: API Data and Database Data

By running both fibers before blocking one of them, we allow operations to run in parallel. Although the API request and database request are blocked in their respective fibers, they do not block the entire application. The total latency is reduced to the longest single operation, which in this case is 3 seconds.

Non-blocking main thread: the application’s main thread remains non-blocking, meaning other tasks or requests can be processed while waiting for I/O operations to complete.

This example illustrates how fibers can help mitigate I/O blocking by allowing multiple I/O-related tasks to run in parallel, ultimately improving the performance and responsiveness of your PHP application.

Example: Using Fibers to Read a Large File Without Blocking

Reading a large file in PHP can take a long time, especially if the file is several gigabytes in size. If this operation is performed in blocking mode, it can cause the application to become unresponsive, especially if it is running in a web server environment where multiple requests need to be processed simultaneously.

Let’s look at how you can use fibers to read a large file piecemeal, allowing the application to remain responsive while the file is being processed.

Here’s how you might traditionally read a large file in PHP:

function readLargeFile($filePath) {
$handle = fopen($filePath, 'r');
if ($handle) {
while (($line = fgets($handle)) !== false) {
// Process each line of the file
echo $line;
}
fclose($handle);
} else {
echo "Error opening file.";
}
}

echo "Starting file read...\n";
readLargeFile('largefile.txt');
echo "File read complete.\n";

Output:

Starting file read...
(Contents of the file are printed here, but the operation blocks until the entire file is read)
File read complete.

In this example, the entire file is read in blocking mode, meaning that the script will not execute until the entire file has been processed. If the file is large, this can significantly delay script execution, causing the application to become unresponsive.

Non-Blocking File Read Using Fibers

Now, let’s modify the script to use Fibers to read the file in chunks. This allows other parts of the application to run while the file is being processed.

$fiber = new Fiber(function ($filePath) {
$handle = fopen($filePath, 'r');
if ($handle) {
while (($line = fgets($handle)) !== false) {
// Simulate yielding control after each line read
Fiber::suspend($line);
}
fclose($handle);
} else {
Fiber::suspend("Error opening file.");
}
});

echo "Starting file read...\n";
$fiber->start('largefile.txt');

while ($fiber->isStarted()) {
$line = $fiber->resume();
if ($line !== false) {
// Process the line
echo $line;
}
}

echo "File read complete.\n";

Output:

Starting file read...
(Line 1 content)
(Line 2 content)
...
File read complete.

Benefits of using fibers to read files
Improved responsiveness: By breaking the file reading process into smaller, non-blocking chunks, the application can remain responsive and handle other tasks in parallel.

Efficient resource utilization: The script does not have to wait for the entire file to be read before proceeding, which can result in more efficient use of CPU and memory resources.

Scalability: This approach scales to very large files because it does not require loading the entire file into memory at once, which would be impractical for very large files.

Conclusion

I/O blocking has long been a problem in PHP development, especially in applications that rely heavily on external data sources or perform multiple I/O operations. With the introduction of Fibers in PHP 8.1, developers now have a powerful tool to address these issues, allowing them to execute non-blocking code more efficiently.

Fibers represent a significant advancement in how we can handle asynchronous tasks and manage concurrency. By providing the ability to suspend and resume code execution at will, fibers open up new possibilities for creating responsive and efficient PHP applications, especially in scenarios where non-blocking, I/O-related operations are critical.

--

--

itsimiro
itsimiro

Written by itsimiro

Passionate developer exploring the realms of software engineering. Writing about tech, coding adventures, and everything in between!

Responses (2)