In this article, we will look at the new Fibers Feature in the future PHP update
PHP continues to improve features in its codebase. PHP Fibers will appear in PHP 8.1 later this year, and this is one of the important additions to the language. They will open up new possibilities for a kind of asynchronous programming (coroutines). The fiber concept mainly refers to the lightweight flow of execution (also called coroutine). They run in parallel, but they are not sent directly to the CPU and are eventually processed by the runtime itself. Many major languages have their own ways of implementing them, but they have one similarity and it is as follows: let the computer perform two or more tasks simultaneously, and wait until everything is completed.
The implementation of Fibers in PHP is not true asynchronous computing, as someone might think. Obviously, PHP core will still be synchronous after Fibers are introduced. You can imagine PHP Fibers are like switching from one car to another.
A Fiber is a single final class and is similar to threads in a computer program. Threads are scheduled by the operating system and does not guarantee when and at which point the threads are paused and resumed. Fibers are created, started, suspended, and terminated by the program itself, and allows fine control of the main program execution and the Fiber execution.
final class Fiber
{
public function __construct(callable $callback) {}
public function start(mixed ...$args): mixed {}
public function resume(mixed $value = null): mixed {}
public function throw(Throwable $exception): mixed {}
public function isStarted(): bool {}
public function isSuspended(): bool {}
public function isRunning(): bool {}
public function isTerminated(): bool {}
public function getReturn(): mixed {}
public static function this(): ?self {}
public static function suspend(mixed $value = null): mixed {}
}
After creating a new Fiber instance with a callable, nothing will happen.
Until you start Fiber, the callback will not be executed like any other normal PHP code.
$fiber = new Fiber(function() : void {
echo "I'm running a Fiber, yay!";
});$fiber->start(); // I'm running a Fiber, yay!
Fibers are asynchronous, but only until you suspend them by calling
Fiber::suspend()
inside the callback. When Fiber::suspend()
$fiber = new Fiber(function() : void {
Fiber::suspend();
echo "I'm running a Fiber, yay!";
});$fiber->start(); // [Nothing happens]
The next
Fiber::resume()
call continues the program from the next$fiber = new Fiber(function() : void {
Fiber::suspend();
echo "I'm running a Fiber, yay!";
});
$fiber->start(); // [Nothing happened]
$fiber->resume(); // I'm running a Fiber, yay!
This is literally incorrect asynchronously, but that doesn't mean your application can't do two things at the same time. The real thing here is that the state of the Fiber function is saved where it was left off. You, figuratively speaking, switch between cars, each going to one point.
One of the great things about
start()
, suspend (and resume()
is thatThe
start()
method will pass arguments to the callable and returnsuspend()
method receives.The
suspend()
method returns any value returned by the resume()
method.The
resume()
method returns whatever is received on the next call tosuspend()
.This makes the relationship between main thread and Fiber relatively
straightforward:
resume()
is used to put the values in Fiber that are received with suspend()
, and suspend()
is used to push values out that are received by resume()
.The official example is become easier to understand now:
$fiber = new Fiber(function (): void {
$value = Fiber::suspend('fiber');
echo "Value used to resume fiber: ", $value, "\n";
});
$value = $fiber->start();
echo "Value from fiber suspending: ", $value, "\n";
$fiber->resume('test');
After executing the above code, you will receive something like this:
Value from fiber suspending: fiber
Value used to resume fiber: test
In truth, PHP works in partnership with nginx / Apache in 99% of cases,
basically, because it is not multi-threaded. The server that enters
PHP blocks and serves only for some tests or shows something to the
client. Fibers allow PHP to work with the socket more efficiently and enable things like WebSockets, server-side events, pooled database connections, or even HTTP / 3 without having to resort to compiling extensions, hacking your way down with unintended functions, encapsulating PHP to another external runtime or any other prescription for disaster.
Some issues may take time to resolve, but if there is a promise to keep a
single code base for other features without spending days trying to compile and deploy, we could accept that.
According to the documentation, Fibers offers "only the minimum required
for custom code to implement full stack coroutines or green streams in PHP." Simply put, unless you have a very strange reason to use them directly, you never have to interact with Fibers as if you were executing coroutines in Javascript or Go.
Some high-level frameworks (like Symfony, Laravel, CodeIgniter, and
CakePHP, among others) will take some time to figure out how to approach Fibers and create a set of tools that they work with from a developer's point of view. Some low-level frameworks like amphp and ReactPHP have already migrated to the Fiber ship in their latest development releases.
However you will be free from thinking more about Fibers and concentrate on your idea, it means that everyone will be creating their own concurrency option with all its benefits and caveats.
I’m going to quote Aaron Piotrowski from PHP Internals Podcast #74:
“Since only one Fiber can be executing at the same time, you don’t have
some of the same race conditions that you have with memory being accessed or written to by two threads at the same time.”
Aaron also adds that frameworks will be able to solve concurrency and
synchronization problems in the same piece of memory.
That’s great because you don't have to think about data races, semaphores and mutexes - things that gophers understand very well. But you are still can operate only two things at a time, no matter what you do.
Based on the fact that only one fiber is running at a time, even if you declare several of them, there is no data synchronization problem. But Aaron said there is a possibility that another Fiber will wake up and rewrite what the first Fiber is sharing. One solution is to use the Go’s channel style.
When Deryk Rethans asked about channels, Aaron replied in simple words:
something else needs to be implemented with Fibers, but until then, the last word on how to use channels will be up to the frameworks if they deem them necessary for what they offer, like guys in amphp.
The Go language has been gaining traction in recent months, especially because it was built on parallelism from scratch. Anything can be done concurrently with the go keyword, and synchronization is done using mutexes or channels, making it easier to work with.
names := make(chan string)
go doFoo(names)
go doBar(names)
From this point of view, Go is well ahead of PHP's original concurrency
solution. If you need something completely multithreaded, you can make your software in Go, or even Rust if you want to use CPU threads directly.
It's not that PHP is incompatible with any concurrency model, but certainly, its foundation is still synchronous at its core due to the fact that it is more convenient and understandable. Compared to Go, the disadvantage of PHP is excessive plumbing.
So if you need a true concurrency model like Go, then PHP will have to be rewritten from scratch, but that will open up a lot of possibilities in the computing world that has already embraced multithreading.
Hopefully, this will allow you to focus more on features and independence, which I talked about a lot.