(PHP 8 >= 8.1.0)
Fibers represent full-stack, interruptible functions. Fibers may be suspended from anywhere in the call-stack, pausing execution within the fiber until the fiber is resumed at a later time.
Fibers pause the entire execution stack, so the direct caller of the function does not need to change how it invokes the function.
Execution may be interrupted anywhere in the call stack using Fiber::suspend() (that is, the call to Fiber::suspend() may be in a deeply nested function or not even exist at all).
Unlike stack-less Generators, each Fiber has its own call stack, allowing them to be paused within deeply nested function calls. A function declaring an interruption point (that is, calling Fiber::suspend()) need not change its return type, unlike a function using yield
which must return a Generator instance.
Fibers can be suspended in any function call, including those called from within the PHP VM, such as functions provided to array_map() or methods called by foreach
on an Iterator object.
Once suspended, execution of the fiber may be resumed with any value using Fiber::resume() or by throwing an exception into the fiber using Fiber::throw(). The value is returned (or exception thrown) from Fiber::suspend().
Note: Prior to PHP 8.4.0, switching fibers during the execution of an object destructor was not allowed.
Example #1 Basic usage
<?php
$fiber = new Fiber(function (): void {
$value = Fiber::suspend('fiber');
echo "Value used to resume fiber: ", $value, PHP_EOL;
});$value = $fiber->start();
echo
"Value from fiber suspending: ", $value, PHP_EOL;$fiber->resume('test');
?>
The above example will output:
Value from fiber suspending: fiber Value used to resume fiber: testuser at csa dot es ¶
3 years ago
Perhaps not using the same variable name everywhere will be a good idea
<?php
$fiber = new Fiber(function (): void {
$parm = Fiber::suspend('fiber');
echo "Value used to resume fiber: ", $parm, PHP_EOL;
});$res = $fiber->start();
echo
"Value from fiber suspending: ", $res, PHP_EOL;$fiber->resume('test');
?>
Ali Madadi ¶
2 years ago
Here is a simple scheduler and thread pool that implements multithreading using fibers and tick functions in PHP 8.1 and returns the return value of each function in the pool in an array at the end.
Note that due to some bugs, you need to register a new tick function for each "thread". Remember to unregister all of them at the end.
The link bellow is the discussion on a bug that is going on right now (At the time of writing this). Note that based on the discussion, the ability to call Fiber::suspend() inside tick function may become forbidden in PHP 8.2+. But if the bug gets fixed, you can move register_tick_function() line to the top of the class, and this simple multithreading class in pure PHP code will work like a charm.
https://github.com/php/php-src/issues/8960
<?phpdeclare(ticks=1);
class
Thread {
protected static $names = [];
protected static $fibers = [];
protected static $params = [];
public static function
register(string|int $name, callable $callback, array $params)
{
self::$names[] = $name;
self::$fibers[] = new Fiber($callback);
self::$params[] = $params;
}
public static function
run() {
$output = [];
while (
self::$fibers) {
foreach (self::$fibers as $i => $fiber) {
try {
if (!$fiber->isStarted()) {
register_tick_function('Thread::scheduler');
$fiber->start(...self::$params[$i]);
} elseif ($fiber->isTerminated()) {
$output[self::$names[$i]] = $fiber->getReturn();
unset(self::$fibers[$i]);
} elseif ($fiber->isSuspended()) {
$fiber->resume();
}
} catch (Throwable $e) {
$output[self::$names[$i]] = $e;
}
}
}
return
$output;
}
public static function
scheduler () {
if(Fiber::getCurrent() === null) {
return;
}if(count(self::$fibers) > 1)
{
Fiber::suspend();
}
}
}?>
And here is an example code on how to use above Thread class:
<?phpfunction thread (string $print, int $loop)
{
$i = $loop;
while ($i--){
echo $print;
}
return
"Thread '{$print}' finished after printing '{$print}' for {$loop} times!";
}foreach(range('A', 'F') as $c) {
Thread::register($c, 'thread', [$c, rand(5, 20)]);
}$outputs = Thread::run();echo PHP_EOL, '-------------- RETURN VALUES --------------', PHP_EOL;
print_r($outputs);?>
The output will be something like this (but probably different):
ABCDEFABCDEFABCDEFABCDEFABCDEFABCEFABFABFABEBEFBEFEFEFAABEABEBEFBEFFAAAAAA
-------------- RETURN VALUES --------------
Array
(
[D] => Thread 'D' finished after printing 'D' for 5 times!
[C] => Thread 'C' finished after printing 'C' for 6 times!
[E] => Thread 'E' finished after printing 'E' for 15 times!
[B] => Thread 'B' finished after printing 'B' for 15 times!
[F] => Thread 'F' finished after printing 'F' for 15 times!
[A] => Thread 'A' finished after printing 'A' for 18 times!
)
maxpanchnko at gmail dot com ¶
2 years ago
One of examples, how to make multi_curl faster twice (pseudocode) using Fibers:
<?php
$curlHandles
= [];
$urls = [
'https://example.com/1',
'https://example.com/2',
...
'https://example.com/1000',
];
$mh = curl_multi_init();
$mh_fiber = curl_multi_init();$halfOfList = floor(count($urls) / 2);
foreach ($urls as $index => $url) {
$ch = curl_init($url);
$curlHandles[] = $ch;$index > $halfOfList ? curl_multi_add_handle($mh_fiber, $ch) : curl_multi_add_handle($mh, $ch);
}$fiber = new Fiber(function (CurlMultiHandle $mh) {
$still_running = null;
do {
curl_multi_exec($mh, $still_running);
Fiber::suspend();
} while ($still_running);
});$fiber->start($mh_fiber);$still_running = null;
do {
$status = curl_multi_exec($mh, $still_running);
} while ($still_running);
do {
$status_fiber = $fiber->resume();
} while (!$fiber->isTerminated());
foreach (
$curlHandles as $index => $ch) {
$index > $halfOfList ? curl_multi_remove_handle($mh_fiber, $ch) : curl_multi_remove_handle($mh, $ch);
}
curl_multi_close($mh);
curl_multi_close($mh_fiber);
?>
nesk at xakep dot ru ¶
2 years ago
I think that in some cases it makes sense to convert a Fiber to a Generator (Coroutine) for convenience. In such cases, this code will be useful:
<?php
function fiber_to_coroutine(\Fiber $fiber): \Generator
{
$index = -1; $value = null;if (!$fiber->isStarted()) {
$value = yield ++$index => $fiber->start();
}if (!$fiber->isTerminated()) {
while (true) {
$value = $fiber->resume($value);if ($fiber->isTerminated()) {
break;
}$value = yield ++$index => $value;
}
}
return
$fiber->getReturn();
}
?>
newuser ¶
2 years ago
Example of the same functionality showing what is the difference between Fiber and Generator
<?php
$gener = (function () use (&$gener): Generator {
$userfunc = function () use (&$gener) : Generator {
register_shutdown_function(function () use (&$gener) {
$gener->send('test');
});
return yield 'test';
};
$parm = yield from $userfunc();
echo "Value used to resume fiber: ", $parm, PHP_EOL;
})();$res = $gener->current();
echo "Value from fiber suspending: ", $res, PHP_EOL;
?>
<?php
$fiber = new Fiber(function () use (&$fiber) : void {
$userfunc = function () use (&$fiber) : string {
register_shutdown_function(function () use (&$fiber) {
$fiber->resume('test');
});
return Fiber::suspend('fiber');
};
$parm = $userfunc();
echo "Value used to resume fiber: ", $parm, PHP_EOL;
});$res = $fiber->start();
echo "Value from fiber suspending: ", $res, PHP_EOL;
?>
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.3