AMPHP ist eine Sammlung ereignisgesteuerter Bibliotheken für PHP, die unter Berücksichtigung von Fibers und Parallelität entwickelt wurden. amphp/amp
stellt speziell Futures und Stornierungen als grundlegende Grundelemente für die asynchrone Programmierung bereit. Wir verwenden jetzt Revolt, anstatt eine Event-Loop-Implementierung mit amphp/amp
auszuliefern.
Amp nutzt stark die mit PHP 8.1 gelieferten Fasern, um asynchronen Code genau wie synchronen, blockierenden Code zu schreiben. Im Gegensatz zu früheren Versionen sind keine Generator-basierten Coroutinen oder Callbacks erforderlich. Ähnlich wie Threads verfügt jede Faser über einen eigenen Aufrufstapel, Fasern werden jedoch gemeinsam von der Ereignisschleife geplant. Verwenden Sie Ampasync()
um Dinge gleichzeitig auszuführen.
Traditionell folgt PHP einem sequentiellen Ausführungsmodell. Die PHP-Engine führt eine Zeile nach der anderen in sequentieller Reihenfolge aus. Oftmals bestehen Programme jedoch aus mehreren unabhängigen Unterprogrammen, die gleichzeitig ausgeführt werden können.
Wenn Sie eine Datenbank abfragen, senden Sie die Anfrage und warten blockierend auf die Antwort vom Datenbankserver. Sobald Sie die Antwort erhalten haben, können Sie mit dem nächsten Schritt beginnen. Anstatt herumzusitzen und nichts zu tun, während wir warten, könnten wir bereits die nächste Datenbankabfrage senden oder einen HTTP-Aufruf an eine API durchführen. Nutzen wir die Zeit, die wir normalerweise mit dem Warten auf I/O verbringen!
Revolt ermöglicht solche gleichzeitigen I/O-Vorgänge. Durch die Vermeidung von Rückrufen halten wir die kognitive Belastung gering. Unsere APIs können wie jede andere Bibliothek verwendet werden, mit der Ausnahme, dass die Dinge auch gleichzeitig funktionieren, da wir unter der Haube nicht blockierende I/O verwenden. Führen Sie Dinge gleichzeitig mit Ampasync()
aus und warten Sie mit Future::await()
auf das Ergebnis, wo und wann Sie es brauchen!
Im Laufe der Jahre gab es verschiedene Techniken zur Implementierung der Parallelität in PHP, z. B. Rückrufe und Generatoren, die in PHP 5 ausgeliefert wurden. Diese Ansätze litten unter dem Problem „Welche Farbe hat Ihre Funktion?“, das wir durch die Auslieferung von Fibers mit PHP 8.1 gelöst haben. Sie ermöglichen die Parallelität mit mehreren unabhängigen Aufrufstapeln.
Fasern werden durch die Ereignisschleife kooperativ geplant, weshalb sie auch Koroutinen genannt werden. Es ist wichtig zu verstehen, dass jeweils nur eine Coroutine ausgeführt wird und alle anderen Coroutinen in der Zwischenzeit angehalten sind.
Sie können Coroutinen mit einem Computer vergleichen, der mehrere Programme auf einem einzigen CPU-Kern ausführt. Jedes Programm erhält einen Zeitschlitz zur Ausführung. Coroutinen sind jedoch nicht präventiv. Sie erhalten nicht ihr festes Zeitfenster. Sie müssen die Kontrolle freiwillig an die Ereignisschleife abgeben.
Jede blockierende E/A-Funktion blockiert den gesamten Prozess, während sie auf E/A wartet. Sie sollten sie vermeiden. Wenn Sie die Installationsanleitung nicht gelesen haben, schauen Sie sich das Hello World-Beispiel an, das die Wirkung von Blockierungsfunktionen demonstriert. Die von AMPHP bereitgestellten Bibliotheken vermeiden das Blockieren von E/A.
Dieses Paket kann als Composer-Abhängigkeit installiert werden.
composer require amphp/amp
Wenn Sie diese Bibliothek verwenden, möchten Sie höchstwahrscheinlich Ereignisse mit Revolt planen, das Sie separat benötigen sollten, auch wenn es automatisch als Abhängigkeit installiert wird.
composer require revolt/event-loop
Diese Pakete stellen die Grundbausteine für asynchrone/gleichzeitige Anwendungen in PHP bereit. Wir bieten viele darauf aufbauende Pakete an, z
amphp/byte-stream
bietet eine Stream-Abstraktionamphp/socket
bietet eine Socket-Schicht für UDP und TCP einschließlich TLSamphp/parallel
bietet parallele Verarbeitung, um mehrere CPU-Kerne zu nutzen und blockierende Vorgänge auszulagernamphp/http-client
der einen HTTP/1.1- und HTTP/2-Client bereitstelltamphp/http-server
der einen HTTP/1.1- und HTTP/2-Anwendungsserver bereitstelltamphp/mysql
und amphp/postgres
für nicht blockierenden DatenbankzugriffDieses Paket erfordert PHP 8.1 oder höher. Keine Erweiterungen erforderlich!
Erweiterungen sind nur erforderlich, wenn Ihre App eine hohe Anzahl gleichzeitiger Socket-Verbindungen erfordert. Normalerweise ist diese Grenze auf bis zu 1024 Dateideskriptoren konfiguriert.
Coroutinen sind unterbrechbare Funktionen. In PHP können sie mithilfe von Fasern implementiert werden.
Hinweis: Frühere Versionen von Amp verwendeten Generatoren für einen ähnlichen Zweck, aber Fasern können an einer beliebigen Stelle im Aufrufstapel unterbrochen werden, wodurch frühere Boilerplates wie
Ampcall()
überflüssig werden.
Zu jedem Zeitpunkt läuft nur eine Faser. Wenn eine Coroutine angehalten wird, wird die Ausführung der Coroutine vorübergehend unterbrochen, sodass andere Aufgaben ausgeführt werden können. Die Ausführung wird wieder aufgenommen, sobald ein Timer abläuft, Stream-Vorgänge möglich sind oder ein erwarteter Future
abgeschlossen ist.
Die Aussetzung und Wiederaufnahme von Coroutinen auf niedriger Ebene wird von Suspension
-API von Revolt übernommen.
<?php
require __DIR__ . ' /vendor/autoload.php ' ;
use Revolt EventLoop ;
$ suspension = EventLoop:: getSuspension ();
EventLoop:: delay ( 5 , function () use ( $ suspension ): void {
print ' ++ Executing callback created by EventLoop::delay() ' . PHP_EOL ;
$ suspension -> resume ( null );
});
print ' ++ Suspending to event loop... ' . PHP_EOL ;
$ suspension -> suspend ();
print ' ++ Script end ' . PHP_EOL ;
In der Revolt-Ereignisschleife registrierte Rückrufe werden automatisch als Coroutinen ausgeführt und können sicher angehalten werden. Neben der Event-Loop-API kann Ampasync()
zum Starten eines unabhängigen Aufrufstapels verwendet werden.
<?php
use function Amp delay ;
require __DIR__ . ' /vendor/autoload.php ' ;
Amp async ( function () {
print ' ++ Executing callback passed to async() ' . PHP_EOL ;
delay ( 3 );
print ' ++ Finished callback passed to async() ' . PHP_EOL ;
});
print ' ++ Suspending to event loop... ' . PHP_EOL ;
delay ( 5 );
print ' ++ Script end ' . PHP_EOL ;
Ein Future
ist ein Objekt, das das letztendliche Ergebnis einer asynchronen Operation darstellt. Es gibt drei Zustände:
Ein erfolgreich abgeschlossener Future entspricht einem Rückgabewert, während ein fehlerhafter Future dem Auslösen einer Ausnahme entspricht.
Eine Möglichkeit, sich asynchronen APIs zu nähern, ist die Verwendung von Rückrufen, die beim Starten des Vorgangs übergeben und nach Abschluss aufgerufen werden:
doSomething ( function ( $ error , $ value ) {
if ( $ error ) {
/* ... */
} else {
/* ... */
}
});
Der Callback-Ansatz hat mehrere Nachteile.
Hier kommen Futures ins Spiel. Sie sind Platzhalter für das Ergebnis, die wie jeder andere Rückgabewert zurückgegeben werden. Der Aufrufer hat die Wahl, das Ergebnis mit Future::await()
oder einen oder mehrere Callbacks zu registrieren.
try {
$ value = doSomething ()-> await ();
} catch (...) {
/* ... */
}
Bei gleichzeitigen Anwendungen gibt es mehrere Futures, bei denen Sie möglicherweise auf alle oder nur auf die erste warten möchten.
AmpFutureawait($iterable, $cancellation)
wartet auf alle Future
Objekte eines iterable
. Wenn in einer der Future
-Instanzen ein Fehler auftritt, wird der Vorgang mit dieser Ausnahme abgebrochen. Andernfalls ist das Ergebnis ein Array, das die Schlüssel aus der iterable
Eingabe mit ihren Vervollständigungswerten übereinstimmt.
Der Kombinator await()
ist äußerst leistungsstark, da er die gleichzeitige Ausführung vieler asynchroner Vorgänge ermöglicht. Schauen wir uns ein Beispiel an, bei dem amphp/http-client
verwendet wird, um mehrere HTTP-Ressourcen gleichzeitig abzurufen:
<?php
use Amp Future ;
use Amp Http Client HttpClientBuilder ;
use Amp Http Client Request ;
$ httpClient = HttpClientBuilder:: buildDefault ();
$ uris = [
" google " => " https://www.google.com " ,
" news " => " https://news.google.com " ,
" bing " => " https://www.bing.com " ,
" yahoo " => " https://www.yahoo.com " ,
];
try {
$ responses = Future await ( array_map ( function ( $ uri ) use ( $ httpClient ) {
return Amp async ( fn () => $ httpClient -> request ( new Request ( $ uri , ' HEAD ' )));
}, $ uris ));
foreach ( $ responses as $ key => $ response ) {
printf (
" %s | HTTP/%s %d %s n" ,
$ key ,
$ response -> getProtocolVersion (),
$ response -> getStatus (),
$ response -> getReason ()
);
}
} catch ( Exception $ e ) {
// If any one of the requests fails the combo will fail
echo $ e -> getMessage (), "n" ;
}
AmpFutureawaitAnyN($count, $iterable, $cancellation)
ist dasselbe await()
außer dass es einzelne Fehler toleriert. Ein Ergebnis wird zurückgegeben, sobald genau $count
Instanzen in der iterable
erfolgreich abgeschlossen wurden. Der Rückgabewert ist ein Array von Werten. Die einzelnen Schlüssel im Komponentenarray bleiben aus der iterable
erhalten, die zur Auswertung an die Funktion übergeben wird.
AmpFutureawaitAll($iterable, $cancellation)
wartet auf alle Futures und gibt ihre Ergebnisse als [$errors, $values]
-Array zurück.
AmpFutureawaitFirst($iterable, $cancellation)
entpackt den ersten abgeschlossenen Future
, unabhängig davon, ob dieser erfolgreich abgeschlossen wurde oder fehlerhaft war.
AmpFutureawaitAny($iterable, $cancellation)
packt den ersten erfolgreich abgeschlossenen Future
aus.
Futures können auf verschiedene Arten erstellt werden. Der meiste Code verwendet Ampasync()
das eine Funktion übernimmt und sie als Coroutine in einer anderen Fiber ausführt.
Manchmal verlangt eine Schnittstelle die Rückgabe eines Future
, die Ergebnisse sind jedoch sofort verfügbar, z. B. weil sie zwischengespeichert sind. In diesen Fällen können Future::complete(mixed)
und Future::error(Throwable)
verwendet werden, um eine sofort abgeschlossene Future
zu konstruieren.
Hinweis Bei der unten beschriebenen
DeferredFuture
-API handelt es sich um eine erweiterte API, die viele Anwendungen wahrscheinlich nicht benötigen. Verwenden Sie nach Möglichkeit stattdessenAmpasync()
oder Kombinatoren.
AmpDeferredFuture
ist für den Abschluss eines ausstehenden Future
verantwortlich. Sie erstellen ein AmpDeferredFuture
und verwenden dessen getFuture
-Methode, um ein AmpFuture
an den Aufrufer zurückzugeben. Sobald das Ergebnis fertig ist, vervollständigen Sie die vom Aufrufer gehaltene Future
mithilfe von complete
oder error
für die verknüpfte DeferredFuture
.
final class DeferredFuture
{
public function getFuture (): Future ;
public function complete ( mixed $ value = null );
public function error ( Throwable $ throwable );
}
Warnung: Wenn Sie
DeferredFuture
-Objekte weitergeben, machen Sie wahrscheinlich etwas falsch. Sie sollen den internen Status Ihres Betriebs widerspiegeln.
Warnung: Sie können eine Zukunft nicht mit einer anderen Zukunft vervollständigen. Verwenden Sie in solchen Fällen
Future::await()
vor dem Aufruf vonDeferredFuture::complete()
.
Hier ist ein einfaches Beispiel für einen asynchronen Wertproduzenten asyncMultiply()
der eine DeferredFuture
erstellt und die zugehörige Future
an seinen Aufrufer zurückgibt.
<?php // Example async producer using DeferredFuture
use Revolt EventLoop ;
function asyncMultiply ( int $ x , int $ y ): Future
{
$ deferred = new Amp DeferredFuture ;
// Complete the async result one second from now
EventLoop:: delay ( 1 , function () use ( $ deferred , $ x , $ y ) {
$ deferred -> complete ( $ x * $ y );
});
return $ deferred -> getFuture ();
}
$ future = asyncMultiply ( 6 , 7 );
$ result = $ future -> await ();
var_dump ( $ result ); // int(42)
Jede Operation, die Abbruch unterstützt, akzeptiert eine Instanz von Cancellation
als Argument. Stornierungen sind Objekte, die es registrierenden Handlern ermöglichen, Stornierungsanfragen zu abonnieren. Diese Objekte werden an Unteroperationen weitergegeben oder müssen von der Operation selbst bearbeitet werden.
$cancellation->throwIfRequested()
kann verwendet werden, um den aktuellen Vorgang mit einer CancelledException
fehlzuschlagen, sobald ein Abbruch angefordert wurde. Während throwIfRequested()
gut funktioniert, möchten einige Vorgänge möglicherweise stattdessen ein Abonnement mit einem Rückruf durchführen. Sie können dies tun, indem Sie Cancellation::subscribe()
verwenden, um eventuell auftretende Stornierungsanfragen zu abonnieren.
Der Anrufer erstellt eine Cancellation
mithilfe einer der folgenden Implementierungen.
Hinweis: Stornierungen haben nur beratenden Charakter. Ein DNS-Resolver ignoriert möglicherweise Abbruchanfragen, nachdem die Anfrage gesendet wurde, da die Antwort ohnehin verarbeitet werden muss und weiterhin zwischengespeichert werden kann. Ein HTTP-Client setzt möglicherweise eine fast abgeschlossene HTTP-Anfrage fort, um die Verbindung wiederzuverwenden, bricht jedoch möglicherweise eine fragmentierte Codierungsantwort ab, da er nicht wissen kann, ob das Fortsetzen tatsächlich günstiger ist als das Abbrechen.
Ein TimeoutCancellations
bricht sich automatisch nach der angegebenen Anzahl von Sekunden ab.
request ( " ... " , new Amp TimeoutCancellation ( 30 ));
Eine SignalCancellation
bricht sich automatisch ab, nachdem ein bestimmtes Signal vom aktuellen Prozess empfangen wurde.
request ( " ... " , new Amp SignalCancellation ( SIGINT ));
Eine DeferredCancellation
ermöglicht den manuellen Abbruch mit dem Aufruf einer Methode. Dies ist die bevorzugte Methode, wenn Sie irgendwo einen benutzerdefinierten Rückruf registrieren müssen, anstatt Ihre eigene Implementierung zu versenden. Nur der Aufrufer hat Zugriff auf DeferredCancellation
und kann den Vorgang mit DeferredCancellation::cancel()
abbrechen.
$ deferredCancellation = new Amp DeferredCancellation ();
// Register some custom callback somewhere
onSomeEvent ( fn () => $ deferredCancellation -> cancel ());
request ( " ... " , $ deferredCancellation -> getCancellation ());
Eine NullCancellation
wird niemals storniert. Der Abbruch ist oft optional, was normalerweise dadurch implementiert wird, dass der Parameter auf Null gesetzt wird. Um Schutzfunktionen wie if ($cancellation)
zu vermeiden, kann stattdessen eine NullCancellation
verwendet werden.
$ cancellation ??= new NullCancellationToken ();
Eine CompositeCancellation
kombiniert mehrere unabhängige Stornierungsobjekte. Wenn eine dieser Stornierungen storniert wird, wird auch die CompositeCancellation
selbst storniert.
amphp/amp
folgt wie alle anderen amphp
Pakete der semantischen Versionierungsspezifikation von Semver.
Kompatible Pakete sollten das amphp
Thema auf GitHub verwenden.
Wenn Sie sicherheitsrelevante Probleme entdecken, senden Sie bitte eine E-Mail an [email protected]
anstatt den Issue-Tracker zu verwenden.
Die MIT-Lizenz (MIT). Weitere Informationen finden Sie unter LICENSE
.