Scala-Schauspieler: Empfangen gegen Reagieren

110

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 reactanstelle von receivedie 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 reactstatt 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 reactschwieriger als mit receive. Das klingt plausibel, da reactes nicht zurückkommt. Das Buch zeigt jedoch weiter, wie Sie mit reactin 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 receivedas reactnicht, 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 reactes 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.

jqno
quelle
2
Siehe auch stackoverflow.com/questions/1526845/…
Seth Tisue

Antworten:

78

Zunächst belegt jeder wartende Schauspieler receiveeinen Thread. Wenn es nie etwas empfängt, wird dieser Thread niemals etwas tun. Ein Schauspieler auf belegt reactkeinen 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 reactkann 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, ist receiveeine Nachricht schneller als eine Nachricht react. Wenn Sie also Schauspieler haben, die viele Nachrichten empfangen, aber nur sehr wenig damit anfangen, kann die zusätzliche Verzögerung von reactzu langsam für Ihre Zwecke sein.

Daniel C. Sobral
quelle
21

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 festzulegen actors.maxPoolSize, um dies herauszufinden).

Einer der offensichtlicheren Gründe, warum der Aufrufstapel verworfen werden muss, ist, dass die loopMethode andernfalls mit a enden würde StackOverflowError. So wie es ist, beendet das Framework a ziemlich geschickt, reactindem es a auslöst SuspendActorException, was vom Schleifencode abgefangen wird, der das dann reacterneut über die andThenMethode ausführt.

Schauen Sie sich die mkBodyMethode in Actorund dann die seqMethode an, um zu sehen, wie sich die Schleife selbst neu terminiert - schrecklich cleveres Zeug!

oxbow_lakes
quelle
20

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

  def a = 10;
  while (! done)  {
     receive {
        case msg =>  println("MESSAGE RECEIVED: " + msg)
     }
     println("after receive and printing a " + a)
  }

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

  def a = 10;
  while (! done)  {
     react {
        case msg =>  println("MESSAGE RECEIVED: " + msg)
     }
     println("after react and printing a " + a) 
  }

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.

Ashwin
quelle
Danke, das war sehr informativ. Aber meintest du nicht +ain den Code-Schnipsel statt +10?
jqno
Gute Antwort. Das verstehe ich auch nicht.
Santiagobasulto
8

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.

Hexren
quelle
Und das zweite Papier. Schlechte Spam-Kontrolle ... :( [Akteure, die Threads und Ereignisse vereinheitlichen ] [2] [2]: lamp.epfl.ch/~phaller/doc/haller07coord.pdf "Akteure, die Threads und Ereignisse
vereinheitlichen
0

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.

user3407472
quelle