Lassen Sie mich zunächst sagen, dass ich viel Java-Erfahrung habe, mich aber erst seit kurzem für funktionale Sprachen interessiere. Vor kurzem habe ich angefangen, mir Scala anzuschauen, was eine sehr schöne Sprache zu sein scheint.
Ich habe jedoch über Scalas Actor-Framework in Programmieren in Scala gelesen , und eines verstehe ich nicht. In Kapitel 30.4 heißt es, dass die Verwendung von react
anstelle von receive
die Wiederverwendung von Threads ermöglicht, was sich positiv auf die Leistung auswirkt, da Threads in der JVM teuer sind.
Bedeutet dies , dass, solange ich zu nennen erinnern react
statt receive
, kann ich so viele Schauspieler , wie ich wie anfangen? Bevor ich Scala entdeckte, habe ich mit Erlang gespielt, und der Autor von Programming Erlang rühmt sich, über 200.000 Prozesse hervorgebracht zu haben, ohne ins Schwitzen zu geraten. Ich würde es hassen, das mit Java-Threads zu tun. Welche Grenzen sehe ich in Scala im Vergleich zu Erlang (und Java)?
Wie funktioniert die Wiederverwendung dieses Threads in Scala? Nehmen wir der Einfachheit halber an, dass ich nur einen Thread habe. Werden alle Akteure, die ich starte, nacheinander in diesem Thread ausgeführt, oder findet eine Art Aufgabenwechsel statt? Wenn ich zum Beispiel zwei Schauspieler starte, die sich gegenseitig Ping-Pong-Nachrichten senden, riskiere ich dann einen Deadlock, wenn sie im selben Thread gestartet werden?
Laut Programming in Scala ist das Schreiben von Schauspielern react
schwieriger als mit receive
. Das klingt plausibel, da react
es nicht zurückkommt. Das Buch zeigt jedoch weiter, wie Sie mit react
in eine Schleife einfügen können Actor.loop
. Als Ergebnis erhalten Sie
loop {
react {
...
}
}
was mir ziemlich ähnlich erscheint
while (true) {
receive {
...
}
}
welches früher in dem Buch verwendet wird. Dennoch heißt es in dem Buch, dass "in der Praxis Programme mindestens einige benötigen receive
". Was vermisse ich hier? Was kann receive
das react
nicht, außer zurückkehren? Und warum kümmert es mich?
Kommen wir zum Kern dessen, was ich nicht verstehe: Das Buch erwähnt immer wieder, wie die Verwendung react
es ermöglicht, den Aufrufstapel zu verwerfen, um den Thread wiederzuverwenden. Wie funktioniert das? Warum muss der Aufrufstapel verworfen werden? Und warum kann der Aufrufstapel verworfen werden, wenn eine Funktion durch Auslösen einer Ausnahme ( react
) beendet wird, aber nicht, wenn sie durch Rückgabe ( receive
) beendet wird ?
Ich habe den Eindruck, dass das Programmieren in Scala einige der wichtigsten Themen hier beschönigt hat, was schade ist, denn ansonsten ist es ein wirklich ausgezeichnetes Buch.
quelle
Antworten:
Zunächst belegt jeder wartende Schauspieler
receive
einen Thread. Wenn es nie etwas empfängt, wird dieser Thread niemals etwas tun. Ein Schauspieler auf belegtreact
keinen Thread, bis er etwas erhält. Sobald es etwas empfängt, wird ihm ein Thread zugewiesen und darin initialisiert.Nun ist der Initialisierungsteil wichtig. Von einem empfangenden Thread wird erwartet, dass er etwas zurückgibt, von einem reagierenden Thread nicht. Der vorherige Stapelzustand am Ende des letzten
react
kann und wird also vollständig verworfen. Wenn der Stapelstatus weder gespeichert noch wiederhergestellt werden muss, kann der Thread schneller gestartet werden.Es gibt verschiedene Leistungsgründe, warum Sie den einen oder anderen möchten. Wie Sie wissen, ist es keine gute Idee, zu viele Threads in Java zu haben. Da Sie jedoch einen Akteur an einen Thread anhängen müssen, bevor dies möglich ist
react
, istreceive
eine Nachricht schneller als eine Nachrichtreact
. Wenn Sie also Schauspieler haben, die viele Nachrichten empfangen, aber nur sehr wenig damit anfangen, kann die zusätzliche Verzögerung vonreact
zu langsam für Ihre Zwecke sein.quelle
Die Antwort lautet "Ja". Wenn Ihre Akteure nichts in Ihrem Code blockieren und Sie verwenden
react
, können Sie Ihr "gleichzeitiges" Programm in einem einzelnen Thread ausführen (versuchen Sie, die Systemeigenschaft festzulegenactors.maxPoolSize
, um dies herauszufinden).Einer der offensichtlicheren Gründe, warum der Aufrufstapel verworfen werden muss, ist, dass die
loop
Methode andernfalls mit a enden würdeStackOverflowError
. So wie es ist, beendet das Framework a ziemlich geschickt,react
indem es a auslöstSuspendActorException
, was vom Schleifencode abgefangen wird, der das dannreact
erneut über dieandThen
Methode ausführt.Schauen Sie sich die
mkBody
Methode inActor
und dann dieseq
Methode an, um zu sehen, wie sich die Schleife selbst neu terminiert - schrecklich cleveres Zeug!quelle
Diese Aussagen über das "Verwerfen des Stapels" haben mich auch für eine Weile verwirrt und ich denke, ich verstehe es jetzt und das ist jetzt mein Verständnis. Im Falle von "Empfangen" blockiert die Nachricht einen dedizierten Thread (mithilfe von object.wait () auf einem Monitor). Dies bedeutet, dass der gesamte Thread-Stapel verfügbar und bereit ist, ab dem Zeitpunkt des "Wartens" beim Empfang von a fortzufahren Botschaft. Zum Beispiel, wenn Sie den folgenden Code hatten
Der Thread würde im Empfangsaufruf warten, bis die Nachricht empfangen wird, und dann fortfahren und die Nachricht "Nach dem Empfangen und Drucken einer 10" mit dem Wert "10" drucken, die sich im Stapelrahmen befindet, bevor der Thread blockiert wird.
Im Falle einer Reaktion gibt es keinen solchen dedizierten Thread, der gesamte Methodenkörper der Reaktionsmethode wird als Abschluss erfasst und von einem beliebigen Thread auf dem entsprechenden Akteur ausgeführt, der eine Nachricht empfängt. Dies bedeutet, dass nur die Anweisungen ausgeführt werden, die allein als Abschluss erfasst werden können, und hier kommt der Rückgabetyp "Nichts" ins Spiel. Betrachten Sie den folgenden Code
Wenn React einen Rückgabetyp von void hätte, würde dies bedeuten, dass es legal ist, Anweisungen nach dem Aufruf "react" zu haben (im Beispiel die println-Anweisung, die die Nachricht "after react and print a 10" druckt), aber in Wirklichkeit ist dies der Fall würde niemals ausgeführt werden, da nur der Hauptteil der "React" -Methode erfasst und zur späteren Ausführung (beim Eintreffen einer Nachricht) sequenziert wird. Da der React-Vertrag den Rückgabetyp "Nothing" hat, können nach React keine Anweisungen erfolgen, und es gibt keinen Grund, den Stack beizubehalten. Im obigen Beispiel müsste die Variable "a" nicht beibehalten werden, da die Anweisungen nach den Reaktionsaufrufen überhaupt nicht ausgeführt werden. Beachten Sie, dass alle erforderlichen Variablen des Reaktionskörpers bereits als Abschluss erfasst wurden, sodass sie problemlos ausgeführt werden können.
Das Java Actor Framework Kilim führt die Stapelwartung tatsächlich durch, indem es den Stapel speichert, der beim Reagieren einer Nachricht abgewickelt wird.
quelle
+a
in den Code-Schnipsel statt+10
?Nur um es hier zu haben:
Ereignisbasierte Programmierung ohne Umkehrung der Steuerung
Diese Artikel sind aus der Scala-API für Schauspieler verknüpft und bilden den theoretischen Rahmen für die Implementierung des Schauspielers. Dies schließt ein, warum die Reaktion möglicherweise niemals zurückkehrt.
quelle
Ich habe keine größeren Arbeiten mit Scala / Akka gemacht, aber ich verstehe, dass es einen sehr signifikanten Unterschied in der Art und Weise gibt, wie Schauspieler geplant sind. Akka ist nur ein intelligenter Threadpool, bei dem die Ausführung von Akteuren zeitlich getrennt wird ... Jedes Mal wird eine Nachricht ausgeführt, die von einem Schauspieler anders als in Erlang ausgeführt wird, was pro Anweisung erfolgen könnte?!
Dies führt mich zu der Annahme, dass das Reagieren besser ist, da es den aktuellen Thread andeutet, andere Akteure für die Planung in Betracht zu ziehen, bei denen der aktuelle Thread beim Empfang "möglicherweise" aktiviert wird, um andere Nachrichten für denselben Akteur weiter auszuführen.
quelle