Fibers, eingeführt in PHP 8.1, sind ein leistungsstarkes Sprachmerkmal, das kooperatives Multitasking in PHP ermöglicht. Sie bieten eine flexible Möglichkeit, die Ausführung von Code zu unterbrechen und später an genau dieser Stelle fortzusetzen, ohne komplexe Callback-Strukturen oder separate Prozesse zu benötigen.
Eine Fiber stellt einen ausführbaren Codeblock dar, dessen Ausführung an beliebigen Stellen unterbrochen und später fortgesetzt werden kann. Im Gegensatz zu Threads laufen Fibers nicht parallel – die Kontrolle wird explizit durch den Entwickler übergeben.
$fiber = new Fiber(function (): void {
echo "Fiber gestartet\n";
$value = Fiber::suspend('unterbrochen');
echo "Fiber fortgesetzt mit Wert: $value\n";
});
$result = $fiber->start();
echo "Hauptprogramm erhielt: $result\n";
$fiber->resume('fortsetzen');
echo "Fiber abgeschlossen\n";Ausgabe:
Fiber gestartet
Hauptprogramm erhielt: unterbrochen
Fiber fortgesetzt mit Wert: fortsetzen
Fiber abgeschlossen
// Eine Fiber erstellen
$fiber = new Fiber(function () {
echo "In der Fiber\n";
return "Fiber-Ergebnis";
});
// Die Fiber starten
$result = $fiber->start();
echo "Fiber beendet mit: $result\n";Mit Fiber::suspend() kann die Ausführung unterbrochen
und ein Wert an den aufrufenden Code zurückgegeben werden:
$fiber = new Fiber(function () {
echo "Schritt 1\n";
Fiber::suspend("Nach Schritt 1");
echo "Schritt 2\n";
Fiber::suspend("Nach Schritt 2");
echo "Schritt 3\n";
return "Abgeschlossen";
});
$result = $fiber->start();
echo "Ergebnis 1: $result\n";
$result = $fiber->resume();
echo "Ergebnis 2: $result\n";
$result = $fiber->resume();
echo "Ergebnis 3: $result\n";Bei der Fortsetzung können Daten an die Fiber übergeben werden:
$fiber = new Fiber(function () {
$received = Fiber::suspend("Daten benötigt");
echo "Fiber erhielt: $received\n";
return "Fertig";
});
$message = $fiber->start();
echo "Fiber sagt: $message\n";
$result = $fiber->resume("Hier sind die Daten");
echo "Abschlussergebnis: $result\n";Exceptions können sowohl innerhalb als auch außerhalb der Fiber behandelt werden:
$fiber = new Fiber(function () {
try {
echo "Vor Exception\n";
Fiber::suspend("Punkt 1");
throw new Exception("Fehler in Fiber");
} catch (Exception $e) {
return "Exception gefangen: " . $e->getMessage();
}
});
try {
$value = $fiber->start();
echo "Fiber unterbrochen mit: $value\n";
$result = $fiber->resume();
echo "Fiber ergebnis: $result\n";
} catch (Throwable $e) {
echo "Exception außerhalb gefangen: " . $e->getMessage() . "\n";
}Fibers sind ideal für die Implementierung von asynchronen I/O-Operationen, bei denen während des Wartens auf Daten andere Aufgaben erledigt werden können:
function asyncRead(string $filename): Fiber {
return new Fiber(function () use ($filename) {
echo "Starte Lesen von $filename...\n";
// Simuliere asynchronen Dateizugriff
Fiber::suspend("Leseoperation gestartet");
// In einer echten Implementierung würde hier ein Event-Loop die Fiber fortsetzen,
// wenn die Daten verfügbar sind
$content = file_get_contents($filename);
return $content;
});
}
// Mehrere Dateien "gleichzeitig" lesen
$fibers = [];
$fileNames = ['file1.txt', 'file2.txt', 'file3.txt'];
foreach ($fileNames as $file) {
$fiber = asyncRead($file);
$status = $fiber->start();
echo "$file: $status\n";
$fibers[$file] = $fiber;
}
// In einer echten Implementierung würde hier ein Event-Loop die Fibers fortsetzen
foreach ($fibers as $file => $fiber) {
$content = $fiber->resume();
echo "$file enthält: " . substr($content, 0, 50) . "...\n";
}Fibers ermöglichen eine verbesserte Version von Generatoren, bei denen Daten in beide Richtungen fließen können:
function sequenceGenerator(): Fiber {
return new Fiber(function () {
$n = 0;
while (true) {
$command = Fiber::suspend($n++);
if ($command === 'reset') {
$n = 0;
} elseif ($command === 'stop') {
break;
}
}
return "Generator beendet";
});
}
$generator = sequenceGenerator();
for ($i = 0; $i < 5; $i++) {
echo $generator->start() . "\n"; // Für den ersten Wert
echo $generator->resume('next') . "\n";
}
echo $generator->resume('reset') . "\n";
echo $generator->resume('next') . "\n";
echo $generator->resume('stop') . "\n";Fibers können verwendet werden, um Datenverarbeitungspipelines zu erstellen, in denen jede Stufe Daten verarbeitet und an die nächste weitergibt:
function createPipelineStage(Closure $processor, ?Fiber $next = null): Fiber {
return new Fiber(function () use ($processor, $next) {
while (true) {
$data = Fiber::suspend("Bereit");
// Daten verarbeiten
$result = $processor($data);
// An die nächste Stufe weiterleiten, falls vorhanden
if ($next !== null) {
$next->resume($result);
} else {
Fiber::suspend($result); // Letztes Ergebnis zurückgeben
}
}
});
}
// Pipeline erstellen
$stage3 = createPipelineStage(fn($data) => "Ergebnis: $data");
$stage2 = createPipelineStage(fn($data) => strtoupper($data), $stage3);
$stage1 = createPipelineStage(fn($data) => "Verarbeitet: $data", $stage2);
// Pipeline initialisieren
$stage3->start();
$stage2->start();
$status = $stage1->start();
echo $status . "\n";
// Daten durch die Pipeline schicken
$stage1->resume("Testdaten");
// Ergebnis aus der letzten Stufe abholen
$result = $stage3->suspend();
echo $result . "\n";PHP verfügte bereits vor der Einführung von Fibers über Generatoren. Beide haben Ähnlichkeiten, unterscheiden sich jedoch in wichtigen Aspekten:
// Generator-Beispiel
function generatorExample() {
yield "Erster Wert";
$received = yield "Zweiter Wert";
yield "Erhielt: $received";
}
$generator = generatorExample();
echo $generator->current() . "\n"; // "Erster Wert"
$generator->next();
echo $generator->current() . "\n"; // "Zweiter Wert"
$generator->send("Hallo Generator");
echo $generator->current() . "\n"; // "Erhielt: Hallo Generator"
// Äquivalentes Fiber-Beispiel
$fiber = new Fiber(function () {
Fiber::suspend("Erster Wert");
$received = Fiber::suspend("Zweiter Wert");
Fiber::suspend("Erhielt: $received");
});
echo $fiber->start() . "\n"; // "Erster Wert"
echo $fiber->resume() . "\n"; // "Zweiter Wert"
echo $fiber->resume("Hallo Fiber") . "\n"; // "Erhielt: Hallo Fiber"Die wichtigsten Unterschiede:
Flexibilität: Fibers können an jeder beliebigen
Stelle im Code unterbrochen werden, auch innerhalb verschachtelter
Funktionsaufrufe. Generatoren können nur durch yield
innerhalb der Generator-Funktion unterbrochen werden.
Verschachtelung: Fibers können andere Fibers aufrufen und unterbrechen, was mit Generatoren komplizierter zu implementieren ist.
Kontrolle: Fibers bieten eine feingranularere Kontrolle über den Ausführungsfluss.
Rückgabewerte: Fibers können komplexere
Rückgabemuster haben und sind nicht auf die yield-Syntax
beschränkt.
Fibers sind besonders wertvoll für die Implementierung von asynchronen Frameworks und Bibliotheken. Sie ermöglichen eine einfachere Nutzung von asynchronem Code, der wie synchroner Code aussieht:
// Hypothetisches asynchrones Framework mit Fibers
$app = new AsyncApp();
// Ohne Fibers (mit Callbacks oder Promises)
$app->get('/data', function ($request, $response) use ($db) {
$db->query('SELECT * FROM users')
->then(function ($users) use ($response) {
return $response->json($users);
})
->catch(function ($error) use ($response) {
return $response->error($error);
});
});
// Mit Fibers (sieht wie synchroner Code aus)
$app->get('/data', function ($request, $response) use ($db) {
try {
$users = $db->query('SELECT * FROM users'); // Asynchron, aber sieht synchron aus
return $response->json($users);
} catch (Exception $e) {
return $response->error($e);
}
});In diesem Beispiel verwendet das hypothetische Framework intern Fibers, um die asynchrone Datenbankabfrage zu ermöglichen, während der Code für den Entwickler wie normaler synchroner Code aussieht.
Das Debugging von Fibers kann komplex sein, da der Ausführungsfluss nicht linear ist:
$fiber = new Fiber(function () {
$a = 1;
echo "Vor Unterbrechung: a = $a\n";
Fiber::suspend();
$a++;
echo "Nach Fortsetzung: a = $a\n";
});
$fiber->start();
// Viele Zeilen Code später...
$fiber->resume();Fibers behalten ihren gesamten Ausführungszustand, was zu erhöhtem Speicherverbrauch führen kann:
function createManyFibers($count) {
$fibers = [];
for ($i = 0; $i < $count; $i++) {
$fibers[] = new Fiber(function () use ($i) {
// Große Datenstrukturen erstellen
$data = array_fill(0, 1000, "Daten für Fiber $i");
Fiber::suspend("Fiber $i erstellt");
// Mehr mit den Daten machen
return "Fiber $i abgeschlossen";
});
}
return $fibers;
}
// Könnte bei vielen Fibers speicherintensiv werden
$fibers = createManyFibers(1000);
foreach ($fibers as $fiber) {
$fiber->start();
}Fibers bieten kooperatives Multitasking, aber keinen echten Parallelismus:
$fiber1 = new Fiber(function () {
// Rechenintensiver Task
for ($i = 0; $i < 1000000; $i++) {
// Diese Schleife blockiert weiterhin, bis sie abgeschlossen ist
// Es gibt keine automatische Unterbrechung
}
return "Fertig";
});
// Fiber2 wird erst ausgeführt, wenn Fiber1 abgeschlossen oder unterbrochen ist
$fiber2 = new Fiber(function () {
return "Ich muss warten, bis Fiber1 unterbrochen wird oder fertig ist";
});Gestalten Sie Ihren Code so, dass die Unterbrechungspunkte klar sind und dokumentieren Sie das erwartete Verhalten:
class AsyncOperation {
private Fiber $fiber;
public function __construct(callable $operation) {
$this->fiber = new Fiber(function () use ($operation) {
$result = null;
try {
// Dokumentierte Unterbrechungspunkte
Fiber::suspend(['status' => 'started']);
// Hauptoperation ausführen
$result = ($operation)();
// Fortschritt melden
Fiber::suspend(['status' => 'completed', 'result' => $result]);
} catch (Throwable $e) {
Fiber::suspend(['status' => 'error', 'error' => $e->getMessage()]);
}
return $result;
});
}
public function start() {
return $this->fiber->start();
}
public function resume() {
if ($this->fiber->isTerminated()) {
throw new RuntimeException("Operation already completed");
}
return $this->fiber->resume();
}
}Verwenden Sie die Fiber-Statusmethoden, um den aktuellen Zustand einer Fiber zu überprüfen:
function fiberStatus(Fiber $fiber): string {
if ($fiber->isStarted()) {
if ($fiber->isTerminated()) {
return "Terminated";
} elseif ($fiber->isSuspended()) {
return "Suspended";
} else {
return "Running";
}
} else {
return "Not started";
}
}
$fiber = new Fiber(function () {
echo "Status in Fiber vor suspend: " . fiberStatus(Fiber::getCurrent()) . "\n";
Fiber::suspend();
echo "Status in Fiber nach resume: " . fiberStatus(Fiber::getCurrent()) . "\n";
});
echo "Status vor Start: " . fiberStatus($fiber) . "\n";
$fiber->start();
echo "Status nach Start: " . fiberStatus($fiber) . "\n";
$fiber->resume();
echo "Status nach Ende: " . fiberStatus($fiber) . "\n";Für komplexe Systeme sollten Sie Fibers in höherer Abstraktion kapseln:
class AsyncScheduler {
private array $readyQueue = [];
private array $waitingIo = [];
public function schedule(Fiber $fiber): void {
$this->readyQueue[] = $fiber;
}
public function waitForIo(string $resource, Fiber $fiber): void {
$this->waitingIo[$resource] = $fiber;
}
public function ioCompleted(string $resource, $data): void {
if (isset($this->waitingIo[$resource])) {
$fiber = $this->waitingIo[$resource];
unset($this->waitingIo[$resource]);
$this->schedule($fiber);
// In einer echten Implementierung würden wir die Daten speichern
// und beim Fortsetzen an die Fiber übergeben
}
}
public function run(): void {
while (!empty($this->readyQueue)) {
$fiber = array_shift($this->readyQueue);
if (!$fiber->isStarted()) {
$fiber->start();
} elseif ($fiber->isSuspended()) {
$fiber->resume();
}
// In einer realen Implementierung würden wir prüfen,
// ob die Fiber beendet oder nur unterbrochen wurde
}
}
}