In diesem Artikel wird zunächst das Konzept des Generators vorgestellt, wobei der Schwerpunkt auf der Nutzung des Ertrags und der Schnittstelle des Generators liegt. Der Coroutine-Teil erläutert kurz die Prinzipien von Coroutinen und Dinge, auf die bei der PHP-Coroutine-Programmierung geachtet werden sollte.
PHP hat seit 5.5 einen Generator (Generator) eingeführt, auf dessen Grundlage eine Coroutine-Programmierung realisiert werden kann. Dieser Artikel beginnt mit einem Überblick über Generatoren und geht dann zur Coroutine-Programmierung über.
Ein Generator ist ein Datentyp, der die Iteratorschnittstelle implementiert. Die Generatorinstanz kann nicht über new abgerufen werden, und es gibt keine statische Methode zum Abrufen der Generatorinstanz. Die einzige Möglichkeit, eine Generatorinstanz zu erhalten, besteht darin, die Generatorfunktion aufzurufen (eine Funktion, die das Schlüsselwort yield enthält). Durch den direkten Aufruf der Generatorfunktion wird ein Generatorobjekt zurückgegeben, und der Code innerhalb der Funktion beginnt mit der Ausführung, wenn der Generator ausgeführt wird.
Gehen Sie zunächst zum Code, um Ertrag und Generator intuitiv zu erleben:
# generator1.php function foo() { exit('exit script when generator runs.'); yield; } $gen = foo(); var_dump($gen); $gen->current(); echo 'unreachable code!'; # 执行结果 object(Generator)#1 (0) { } exit script when generator runs.
foo
Die Funktion enthält das Schlüsselwort yield
und wandelt sich in eine Generatorfunktion um. Der Aufruf von foo
führt keinen Code im Funktionskörper aus, sondern gibt stattdessen eine Generatorinstanz zurück. Nachdem der Generator ausgeführt wurde, wird der Code in der Funktion foo
ausgeführt und das Skript beendet.
Wie der Name schon sagt, können Generatoren zur Generierung von Daten verwendet werden. Die Art und Weise, wie Daten generiert werden, unterscheidet sich lediglich von anderen Funktionen: Der Generator gibt Daten über yield
statt über return
zurück. Nachdem yield
Daten zurückgegeben hat, wird die Generatorfunktion nicht zerstört, sondern pausiert lediglich ihren Betrieb. Sie können die Ausführung in der Zukunft fortsetzen; der Generator wird einmal ausgeführt und gibt (nur) einen Datenwert zurück. Wenn der Generator nicht aufgerufen wird, um Daten zu erhalten Der Code im Generator bleibt jedes Mal stehen, beispielsweise so. So sehen die vom Generator generierten Daten aus.
Der Generator implementiert die Iterator-Schnittstelle. Sie können die foreach
Schleife oder die manuelle current/next/valid
verwenden, um die Generatordaten zu erhalten. Der folgende Code demonstriert die Datengenerierung und -durchquerung:
# generator2.php function foo() { # 返回键值对数据 yield "key1" => "value1"; $count = 0; while ($count < 5) { # 返回值,key自动生成 yield $count; ++ $count; } # 不返回值,相当于返回null yield; } # 手动获取生成器数据 $gen = foo(); while ($gen->valid()) { fwrite(STDOUT, "key:{$gen->key()}, value:{$gen->current()}\n"); $gen->next(); } # foreach 遍历数据 fwrite(STDOUT, "\ndata from foreach\n"); foreach (foo() as $key => $value) { fwrite(STDOUT, "key:$key, value:$value\n"); }
yield
Das Schlüsselwort ist der Kern des Generators, der es gewöhnlichen Funktionen ermöglicht, sich in Generatorfunktionen zu differenzieren (zu entwickeln). yield
bedeutet „aufgeben“. Wenn das Programm die yield
-Anweisung erreicht, wird die Ausführung angehalten, die CPU wird aufgegeben und die Kontrolle wird an den Aufrufer zurückgegeben, und die Ausführung wird beim nächsten Mal am Unterbrechungspunkt fortgesetzt es wird ausgeführt. Wenn die Kontrolle an den Aufrufer zurückgegeben wird, kann die yield
-Anweisung den Wert an den Aufrufer zurückgeben. generator2.php
Das Skript zeigt drei Formen von Yield-Rückgabewerten:
yield $key => $value: gibt den Schlüssel und den Wert der Daten zurück; >
Yield $value: Gibt Daten zurück, Schlüssel wird vom System zugewiesen-Funktion des Generators bereitgestellt: Die Variable, die auf der linken Seite von
erscheint, erhält den vonfunction logger(string $filename) { $fd = fopen($filename, 'w+'); while($msg = yield) { fwrite($fd, date('Y-m-d H:i:s') . ':' . $msg . PHP_EOL); } fclose($fd); } $logger = logger('log.txt'); $logger->send('program starts!'); // do some thing $logger->send('program ends!');
yield
send
ermöglicht die bidirektionale Datenkommunikation zwischen Generatoren und der Außenwelt: yield
gibt Daten zurück; send
bietet Unterstützung für fortlaufende Betriebsdaten . Da send
es dem Generator ermöglicht, die Ausführung fortzusetzen, ähnelt dieses Verhalten der -Schnittstelle des Iterators und entspricht send
. yield
send
Andere send
next
next
send(null)
Die PHP5-Generatorfunktion kann keinen $string = yield $data;
-Wert haben. Nach PHP7 kann sie einen Wert zurückgeben und den zurückgegebenen Wert über $string = (yield $data)
des Generators abrufen.
PHP7 fügt eine neue return
-Syntax hinzu, um die Generatordelegierung zu implementieren. getReturn
kann nach dem Start nicht mehr aufgerufen werden. yield from
rewind
Im Vergleich zu anderen Iteratoren zeichnen sich Generatoren durch einen geringen Leistungsaufwand und eine einfache Codierung aus. Seine Rolle spiegelt sich hauptsächlich in drei Aspekten wider:
zu lesen: PHP Generator, interessant und leicht zu verstehen.
Coroutine ist eine Unterroutine, die jederzeit unterbrochen und fortgesetzt werden kann. Das Schlüsselwort ermöglicht der Funktion diese Fähigkeit, sodass sie für die Coroutine-Programmierung verwendet werden kann. .
Prozesse, Threads und CoroutinenThreads gehören zu Prozessen und ein Prozess kann mehrere Threads haben. Ein Prozess ist die kleinste Einheit für die Computerzuweisung von Ressourcen, und ein Thread ist die kleinste Einheit für die Computerplanung und -ausführung. Sowohl Prozesse als auch Threads werden vom Betriebssystem geplant.yield
Coroutinen können als „Benutzermodus-Threads“ betrachtet werden, die Benutzerprogramme zur Implementierung der Planung erfordern. Threads und Prozesse werden vom Betriebssystem so geplant, dass sie abwechselnd auf „präventive“ Weise ausgeführt werden, und Coroutinen überlassen aktiv die CPU, um abwechselnd auf „negative“ Weise ausgeführt zu werden. Coroutinen sind sehr leicht, der Coroutine-Wechsel erfordert keinen Thread-Wechsel und die Ausführungseffizienz ist hoch. Je größer die Anzahl, desto besser können die Vorteile von Coroutinen widergespiegelt werden.
Die vom Generator implementierte Coroutine ist eine stapellose Coroutine, das heißt, die Generatorfunktion verfügt nur über einen Funktionsrahmen, der zur Laufzeit zur Ausführung an den Stapel des Aufrufers angehängt wird. Im Gegensatz zur leistungsstarken Stack-Coroutine kann der Generator die Richtung des Programms nicht steuern, nachdem es angehalten wurde, und kann die Kontrolle nur passiv an den Aufrufer zurückgeben. Der Generator kann sich nur selbst unterbrechen, nicht die gesamte Coroutine. Der Vorteil des Generators besteht natürlich darin, dass er sehr effizient ist (Sie müssen nur den Programmzähler speichern, wenn Sie pausieren) und einfach zu implementieren ist.
Apropos Coroutine-Programmierung in PHP: Ich glaube, die meisten Leute haben diesen von Bruder Niao nachgedruckten (übersetzten) Blog-Beitrag bereits gelesen: Mit Coroutinen mehrere Funktionen in der PHP-Aufgabenplanung erreichen. Der ursprüngliche Autor Nikic ist der Kernentwickler von PHP, der Initiator und Implementierer der Generatorfunktion. Wenn Sie mehr über Generatoren und die darauf basierende Coroutine-Programmierung erfahren möchten, sind Nikics RFC zu Generatoren und die Artikel auf der Website von Niaoge ein Muss.
Schauen wir uns zunächst an, wie Generator-basierte Coroutinen funktionieren: Coroutinen arbeiten kollaborativ, das heißt, Coroutinen geben aktiv die CPU ab, um eine abwechselnde Ausführung mehrerer Aufgaben zu erreichen (also gleichzeitiges Multitasking, aber nicht parallel A). Der Generator kann als Coroutine betrachtet werden. Wenn die Anweisung yield
ausgeführt wird, wird die CPU-Steuerung an den Aufrufer zurückgegeben und der Aufrufer führt weiterhin andere Coroutinen oder andere Codes aus.
Schauen wir uns die Schwierigkeit an, den Blog von Bruder Bird zu verstehen. Coroutinen sind sehr leichtgewichtig und Tausende von Coroutinen (Generatoren) können gleichzeitig in einem System existieren. Das Betriebssystem plant keine Coroutinen und die Arbeit, die Coroutinenausführung zu organisieren, obliegt den Entwicklern. Einige Leute verstehen den Coroutine-Teil des Artikels von Bruder Niao nicht, weil er sagt, dass es wenig Coroutine-Programmierung gibt (das Schreiben von Coroutinen bedeutet hauptsächlich das Schreiben von Generatorfunktionen), aber sie verbringen viel Zeit mit der Implementierung eines Coroutine-Schedulers (Scheduler oder Kernel): Simuliert das Betriebssystem und führt eine faire Planung für alle Coroutinen durch. Der allgemeine Gedanke bei der PHP-Entwicklung ist: Ich habe diese Codes geschrieben und die PHP-Engine ruft meine Codes auf, um die erwarteten Ergebnisse zu erzielen. Coroutine-Programmierung erfordert nicht nur das Schreiben von Code, um die Arbeit zu erledigen, sondern auch das Schreiben von Code, um diesen Codes anzuweisen, wann sie arbeiten sollen. Wenn Sie die Denkweise des Autors nicht gut verstehen, wird es natürlich schwieriger sein, sie zu verstehen. Es muss selbst geplant werden, was einen Nachteil der Generator-Coroutine im Vergleich zur nativen Coroutine (Async/Wait-Form) darstellt.
Da wir nun wissen, was Coroutine ist, wofür kann sie verwendet werden? Die Coroutine gibt die CPU selbstständig auf, um zusammenzuarbeiten und die CPU effizient zu nutzen. Natürlich sollte der Zeitpunkt zum Aufgeben liegen, wenn das Programm blockiert ist. Wo wird das Programm blockieren? Benutzermoduscode blockiert selten und wird hauptsächlich durch Systemaufrufe verursacht. Die meisten Systemaufrufe sind E/A-Aufrufe, daher ist das Hauptanwendungsszenario von Coroutinen die Netzwerkprogrammierung. Um eine hohe Leistung und Parallelität des Programms zu gewährleisten, sollte das Programm asynchron ausgeführt werden und nicht blockieren. Da die asynchrone Ausführung Benachrichtigungen und Rückrufe erfordert, kann das Schreiben von Rückruffunktionen das Problem der „Rückrufhölle“ nicht vermeiden: Die Lesbarkeit des Codes ist schlecht und der Programmausführungsprozess ist auf die Schichten von Rückruffunktionen verteilt. Es gibt zwei Möglichkeiten, die Callback-Hölle zu lösen: Promise und Coroutinen. Coroutinen können Code synchron schreiben und werden in der Hochleistungsnetzwerkprogrammierung (IO-intensiv) empfohlen.
Werfen wir einen Blick zurück auf die Coroutine-Programmierung in PHP. In PHP wird die Coroutine-Programmierung auf Basis von Generatoren implementiert. Es wird empfohlen, Coroutine-Frameworks wie RecoilPHP
und Amp
zu verwenden. Diese Frameworks verfügen bereits über geschriebene Scheduler. Wenn Sie eine Generatorfunktion direkt darauf entwickeln, plant der Kernel automatisch die Ausführung (wenn Sie möchten, dass eine Funktion in einem Coroutine-Modus ausgeführt wird, fügen Sie einfach yield
in den Funktionskörper ein). Wenn Sie die yield
-Methode nicht für die Coroutine-Programmierung verwenden möchten, empfehlen wir swoole
oder sein abgeleitetes Framework, mit dem Sie ein Golang-ähnliches Coroutine-Programmiererlebnis erzielen und gleichzeitig die Entwicklungseffizienz von PHP genießen können.
Wenn Sie die ursprüngliche PHP-Coroutine-Programmierung verwenden möchten, ist ein Scheduler ähnlich dem im Blog von Niao Ge unerlässlich. Der Scheduler plant die Ausführung der Coroutine. Nachdem die Coroutine unterbrochen wurde, kehrt die Kontrolle zum Scheduler zurück. Daher sollte sich der Scheduler immer in der Hauptschleife (Ereignisschleife) befinden, d. h. wenn die CPU die Coroutine nicht ausführt, sollte sie den Scheduler-Code ausführen. Bei der Ausführung ohne Coroutine sollte sich der Scheduler selbst blockieren, um eine CPU-Belastung zu vermeiden (Niao Ges Blog verwendet den integrierten Systemaufruf select
), auf das Eintreffen des Ereignisses warten und dann die entsprechende Coroutine ausführen. Während der Ausführung des Programms sollte die Coroutine, mit Ausnahme der Blockierung durch den Scheduler, während der Ausführung keine blockierenden APIs aufrufen.
Bei der Coroutine-Programmierung besteht die Hauptaufgabe von yield
darin, die Kontrolle zu übertragen, ohne sich um den Rückgabewert zu kümmern (im Grunde wird der von yield
zurückgegebene Wert beim nächsten Mal ausgeführt direkt send
wenn es soweit ist). Der Schwerpunkt sollte auf dem Zeitpunkt der Kontrollübertragung und der Funktionsweise der Coroutine liegen.
Ein weiterer zu beachtender Punkt ist, dass Coroutinen wenig mit Asynchronität zu tun haben, sondern auch von der Unterstützung der Betriebsumgebung abhängt. In der herkömmlichen PHP-Betriebsumgebung wird Promise/Coroutine immer noch synchron blockiert, selbst wenn Promise/Coroutine verwendet wird. Egal wie großartig das Coroutine-Framework ist, sleep
es wird überhaupt nicht funktionieren. Als Analogie: Auch wenn JavaScript keine Promise/Async-Technologien verwendet, ist es asynchron und nicht blockierend.
Durch Generatoren und Promise kann eine Coroutine-Programmierung ähnlich wie await
implementiert werden. Auf Github gibt es viele relevante Codes, die in diesem Artikel nicht behandelt werden.
Verwandte Empfehlungen:
$_SERVER in PHP Detaillierte Einführung
Detaillierte Einführung in Output_Buffering in PHP, Outputbuffering_PHP-Tutorial
Das obige ist der detaillierte Inhalt vonDetaillierte Einführung in Coroutinen in PHP (Code). Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!