Skip to content

Instantly share code, notes, and snippets.

@m6w6
Last active October 5, 2022 21:56
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save m6w6/7b26c52222254c81814c02859d18f978 to your computer and use it in GitHub Desktop.
Save m6w6/7b26c52222254c81814c02859d18f978 to your computer and use it in GitHub Desktop.
Fibers are...

Fibers are...

  +------------------------------------------------------------+
  | Process                                                    |
  |   +--------------------------------------------------------+
  |   | Threads (OS/User)                                      |
  |   |   +----------------------------------------------------+
  |   |   | Coroutines                                         |
  |   |   |   + -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  - +
  |   |   |   | ┌ Fiber (with stack, yield anywhere)           |
  |   |   |     +                                              |
  |   |   |   | └ Generator (stackless, top-level yield)       |
  +---+---+---+------------------------------------------------+

not Threads

Threads

  • are preemptively scheduled by the OS (external scheduler is allowed to interrupt execution of a task)
  • may yield voluntarily to suggest to interrupt execution of its task
  • run in parallel (as long as there are not more active threads than cores)
  • run in percieved random order

a Kind of Coroutine

like Generators, they

  • are cooperatively scheduled by the program
  • must suspend themselves to yield execution to other tasks
  • run concurrently, i.e. only one at any given time
  • run in deterministic order (from the POV of the program)

unlike Generators, they

  • do not force a specific return type for functions declaring interruption points
    • Generator
      $g = (function() : Generator {
          yield;
          return 123;
      })();
    • Fiber
      $f = new Fiber(function() : mixed {
          Fiber::suspend();
          return 123;
      });
  • have a full function call stack
    • can suspend their execution from any nested function call, compared to Generators' yield only suspending from top level
      • Generator
        // this yield of g2 only bubbles up to 
        // the program because g1 decides to do so
        function g2() : Generator { yield 2; }
        // this yield of g1 is the actual and
        // only true interruption point
        function g1(Generator $sub) : Generator { yield from $sub; }
        foreach (g1(g2()) as $y) {
            // ...
        }
      • Fiber
        function s(callable $f = null) { Fiber::suspend(); if ($f) $f(); }
        // two "yields" at different function call depths,
        // without any involvement of the actual fiber function
        function f() { s(s(...)); } 
        $f = new Fiber(f(...));
        for ($f->start(); !$f->isTerminated(); $f->resume()) {
            // ...
        }

new in 8.1

Threads Generators Fibers
available in PHP 7.2 - 8.0 [1] 5.5+ 8.1+
model parallel concurrent concurrent
complexity high awkward reasonable
best used for parallel computation dynamic iterators asynchronous I/O
are not a hammer like PHP comprehensible as coroutines [2] async/await, yet

[1] ext-parallel with thread safe PHP, http://pecl.php.net/package/parallel
[2] https://www.npopov.com/2012/12/22/Cooperative-multitasking-using-coroutines-in-PHP.html

interesting?

TS;WTRM

<?php
function apply($call, ...$obj) {
$r = [];
foreach ($obj as $o) {
$r[] = $call($o);
}
return $r;
}
$all = function($call) use(&$apply) {
return array_reduce($apply($call), fn($a, $b) => $a && $b, true);
};
$any = function($call) use(&$apply) {
return array_reduce($apply($call), fn($a, $b) => $a || $b, false);
};
<?php
require_once "~exmaple_boilerplate.php";
$f = function(array &$ref, $id) {
return function() use(&$ref, $id) {
while (count($ref) < 10) {
Fiber::suspend($ref[] = $id);
}
};
};
$ar = [];
$apply = (fn(...$f) => fn($call) => apply($call, ...$f))(new Fiber($f($ar, 1)), new Fiber($f($ar, 2)));
$apply(fn($o) => $o->start($ar));
while ($any(fn($o) => !$o->isTerminated())) {
$apply(fn($o) => $o->resume());
}
print_r($ar);
/* Array
(
[0] => 1
[1] => 2
[2] => 1
[3] => 2
[4] => 1
[5] => 2
[6] => 1
[7] => 2
[8] => 1
[9] => 2
) */
<?php
require_once "~exmaple_boilerplate.php";
$g = function(array &$ref, $id) : Generator {
while (count($ref) < 10) {
yield $ref[] = $id;
}
};
$ar = [];
$apply = (fn(...$g) => fn($call) => apply($call, ...$g))($g($ar, 1), $g($ar, 2));
$apply(fn($o) => $o->rewind());
while ($all(fn($o) => $o->valid())) {
// printf("%d %d \n", ...$apply(fn($o) => $o->current()));
$apply(fn($o) => $o->next());
}
print_r($ar);
/* Array
(
[0] => 1
[1] => 2
[2] => 1
[3] => 2
[4] => 1
[5] => 2
[6] => 1
[7] => 2
[8] => 1
[9] => 2
) */
<?php
require_once "~exmaple_boilerplate.php";
use parallel\Channel;
use function parallel\run;
$chan = new Channel;
$prod = function(Channel $chan, int $id) {
$running = true;
while ($running) try {
$chan->send($id);
} catch (Throwable) {
$running = false;
}
};
/** @var parallel\Future $recv */
$recv = run(function(Channel $chan) {
$ar = [];
while (true) {
$ar[] = $chan->recv();
if (count($ar) >= 100) {
$chan->close();
return $ar;
}
}
}, [$chan]);
run($prod, [$chan, 1]);
run($prod, [$chan, 2]);
print_r($recv->value());
/* Array
(
[0] => 1
[1] => 1
[2] => 1
...
[99] => 1 // or maybe it flips to 2 at index 42 if you're "lucky" ;)
) */
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment