Wenn ich eine Go-Bibliothek portieren wollte, die Goroutinen verwendet, wäre Scala eine gute Wahl, da das Posteingang / Akka-Framework Coroutinen ähnelt?
Wenn ich eine Go-Bibliothek portieren wollte, die Goroutinen verwendet, wäre Scala eine gute Wahl, da das Posteingang / Akka-Framework Coroutinen ähnelt?
Antworten:
Nein, das sind sie nicht. Goroutinen basieren auf der Theorie der Kommunikation sequentieller Prozesse, wie sie 1978 von Tony Hoare spezifiziert wurde. Die Idee ist, dass es zwei Prozesse oder Threads geben kann, die unabhängig voneinander agieren, aber einen "Kanal" gemeinsam nutzen, in den ein Prozess / Thread Daten einfügt in und der andere Prozess / Thread verbraucht. Die bekanntesten Implementierungen sind die Kanäle von Go und Clojure. Derzeit core.async
sind sie jedoch auf die aktuelle Laufzeit beschränkt und können nicht verteilt werden, auch nicht zwischen zwei Laufzeiten auf derselben physischen Box.
CSP wurde entwickelt, um eine statische, formale Prozessalgebra zum Nachweis der Existenz von Deadlocks im Code aufzunehmen. Dies ist eine wirklich nette Funktion, die aber weder von Goroutinen noch core.async
derzeit unterstützt wird. Wenn und wann dies der Fall ist, ist es äußerst hilfreich zu wissen, bevor Sie Ihren Code ausführen, ob ein Deadlock möglich ist oder nicht. Da CSP die Fehlertoleranz jedoch nicht auf sinnvolle Weise unterstützt, müssen Sie als Entwickler herausfinden, wie mit Fehlern umgegangen werden kann, die auf beiden Seiten der Kanäle auftreten können, und diese Logik wird in der gesamten Anwendung verbreitet.
Bei den von Carl Hewitt 1973 angegebenen Akteuren handelt es sich um Unternehmen, die über ein eigenes Postfach verfügen. Sie sind von Natur aus asynchron und haben eine Standorttransparenz, die sich über Laufzeiten und Maschinen erstreckt. Wenn Sie eine Referenz (Akka) oder PID (Erlang) eines Akteurs haben, können Sie eine Nachricht senden. Hier finden einige Leute auch Fehler in actor-basierten Implementierungen, da Sie einen Verweis auf den anderen Akteur haben müssen, um ihm eine Nachricht zu senden, wodurch Sender und Empfänger direkt gekoppelt werden. Im CSP-Modell wird der Kanal gemeinsam genutzt und kann von mehreren Herstellern und Verbrauchern gemeinsam genutzt werden. Nach meiner Erfahrung war dies kein großes Problem. Ich mag die Idee von Proxy-Referenzen, die bedeuten, dass mein Code nicht mit Implementierungsdetails zum Senden der Nachricht übersät ist - ich sende nur eine, und wo immer sich der Akteur befindet, empfängt er sie.
Schauspieler haben eine weitere sehr schöne Eigenschaft - Fehlertoleranz. Durch die Organisation von Akteuren in einer Überwachungshierarchie gemäß der in Erlang entwickelten OTP-Spezifikation können Sie eine Fehlerdomäne in Ihre Anwendung einbauen. Genau wie Wertklassen / DTOs / wie auch immer Sie sie nennen möchten, können Sie Fehler modellieren, wie sie behandelt werden sollen und auf welcher Hierarchieebene. Dies ist sehr leistungsfähig, da Sie innerhalb von CSP nur über sehr wenige Funktionen zur Fehlerbehandlung verfügen.
Akteure sind auch ein Parallelitätsparadigma, bei dem der Akteur einen veränderlichen Status in sich haben kann und garantiert, dass kein Multithread-Zugriff auf den Status erfolgt, es sei denn, der Entwickler, der ein auf Akteuren basierendes System erstellt, führt ihn versehentlich ein, indem er beispielsweise den Akteur als Listener registriert für einen Rückruf oder asynchron innerhalb des Schauspielers über Futures.
Schamloser Plug - Ich schreibe mit dem Leiter des Akka-Teams, Roland Kuhn, ein neues Buch mit dem Titel Reactive Design Patterns, in dem wir all dies und mehr diskutieren. Grüne Fäden, CSP, Ereignisschleifen, Iterate, reaktive Erweiterungen, Akteure, Futures / Versprechen usw. Erwarten Sie Anfang nächsten Monats einen MEAP zu Manning.
Viel Glück!
<-
warten, bis sie fertig ist, bevor Sie fortfahren . Akka hat eine weniger mächtige Form davon ask
, ist aber ask
nicht wirklich die Akka-Art IMO. Chans werden ebenfalls eingegeben, Postfächer hingegen nicht.
Hier gibt es zwei Fragen:
goroutines
?Dies ist eine einfache Frage, da Scala eine Allzwecksprache ist, die nicht schlechter oder besser ist als viele andere, die Sie als "Port-Goroutinen" auswählen können.
Es gibt natürlich viele Meinungen darüber, warum Scala als Sprache besser oder schlechter ist (z. B. hier ist meine), aber dies sind nur Meinungen, und lassen Sie sich nicht davon aufhalten. Da Scala ein allgemeiner Zweck ist, kommt es "so ziemlich" darauf an: Alles, was Sie in Sprache X tun können, können Sie in Scala tun. Wenn es zu breit klingt .. wie wäre es mit Fortsetzungen in Java :)
goroutines
?Die einzige Ähnlichkeit (abgesehen vom Nitpicking) besteht darin, dass beide mit Parallelität und Nachrichtenübermittlung zu tun haben. Aber hier endet die Ähnlichkeit.
Da Jamies Antwort einen guten Überblick über Scala-Schauspieler gab, werde ich mich mehr auf Goroutines / core.async konzentrieren, aber mit einigen Schauspieler-Modell-Intro.
Wo ein „sorgenfreie“ Stück ist in der Regel im Zusammenhang mit Begriffen wie: fault tolerance
, resiliency
, availability
, etc ..
Ohne ins Detail zu gehen, wie Schauspieler arbeiten, haben Schauspieler in zwei einfachen Worten zu tun mit:
Denken Sie an "sprechende Prozesse", bei denen jeder Prozess eine Referenz und eine Funktion hat, die aufgerufen wird, wenn eine Nachricht eintrifft.
Natürlich steckt noch viel mehr dahinter (z. B. Erlang OTP oder Akka Docs ), aber die beiden oben genannten sind ein guter Anfang.
Wo es mit Schauspielern interessant wird, ist .. Umsetzung. Zwei große sind derzeit Erlang OTP und Scala AKKA. Während beide darauf abzielen, dasselbe zu lösen, gibt es einige Unterschiede. Schauen wir uns ein paar an:
Ich verwende absichtlich keinen Jargon wie "referentielle Transparenz", "Idempotenz" usw. Sie nützen nichts, außer Verwirrung zu stiften. Sprechen wir also einfach über Unveränderlichkeit [ein can't change that
Konzept]. Erlang als Sprache ist eine Meinung und neigt zu einer starken Unveränderlichkeit, während es in Scala zu einfach ist, Akteure dazu zu bringen, ihren Zustand zu ändern / zu mutieren, wenn eine Nachricht empfangen wird. Es wird nicht empfohlen, aber Veränderlichkeit in Scala befindet sich direkt dort vor Ihnen, und die Leute haben es zu verwenden.
Ein weiterer interessanter Punkt, über den Joe Armstrong spricht, ist die Tatsache, dass Scala / AKKA durch die JVM begrenzt ist, die einfach nicht wirklich darauf ausgelegt war, "verteilt" zu werden, während Erlang VM dies war. Es hat mit vielen Dingen zu tun, wie zum Beispiel: Prozessisolation pro Prozess im Vergleich zur gesamten VM-Garbage Collection, Laden von Klassen, Prozessplanung und anderen.
Das oben Gesagte soll nicht sagen, dass eines besser ist als das andere, sondern es soll zeigen, dass die Reinheit des Akteurmodells als Konzept von seiner Implementierung abhängt.
Nun zu den Goroutinen.
Wie andere bereits erwähnte Antworten zeigen, wurzeln Goroutinen in der Kommunikation sequentieller Prozesse , einer "formalen Sprache zur Beschreibung von Interaktionsmustern in gleichzeitigen Systemen", die per Definition so ziemlich alles bedeuten kann :)
Ich werde Beispiele geben, die auf core.async basieren , da ich Interna besser kenne als Goroutines. core.async
Wurde aber nach dem Goroutines / CSP-Modell gebaut, so sollte es konzeptionell nicht zu viele Unterschiede geben.
Das Hauptprimitiv für die Parallelität in core.async / Goroutine ist a channel
. Stellen Sie sich eine channel
als "Warteschlange auf Felsen" vor. Dieser Kanal wird zum "Weiterleiten" von Nachrichten verwendet. Jeder Prozess, der "an einem Spiel teilnehmen" möchte, erstellt oder erhält einen Verweis auf a channel
und sendet / nimmt (z. B. sendet / empfängt) Nachrichten an ein / von diesem.
Die meiste Arbeit, die an Kanälen ausgeführt wird, findet normalerweise innerhalb einer " Goroutine " oder eines " Go-Blocks " statt, die " ihren Körper nimmt und ihn auf Kanaloperationen untersucht. Er verwandelt den Körper in eine Zustandsmaschine. Die Zustandsmaschine wird 'geparkt' und der eigentliche Steuerungsthread wird freigegeben. Dieser Ansatz ähnelt dem in C # async verwendeten. Wenn der Blockierungsvorgang abgeschlossen ist, wird der Code fortgesetzt (auf einem Thread-Pool-Thread oder dem einziger Thread in einer JS-VM) "( Quelle ).
Es ist viel einfacher, mit einem Bild zu vermitteln. So sieht eine blockierende E / A-Ausführung aus:
Sie können sehen, dass Threads meistens Zeit damit verbringen, auf die Arbeit zu warten. Hier ist die gleiche Arbeit, die jedoch über den Ansatz "Goroutine" / "go block" durchgeführt wurde:
Hier haben 2 Threads die ganze Arbeit erledigt, die 4 Threads in einem blockierenden Ansatz erledigt haben, während sie die gleiche Zeit in Anspruch genommen haben.
Der Kicker in der obigen Beschreibung lautet: "Threads werden geparkt ", wenn sie keine Arbeit haben, was bedeutet, dass ihr Status auf eine Zustandsmaschine "ausgelagert" wird und der tatsächliche Live-JVM-Thread andere Arbeiten ausführen kann ( Quelle für ein großartiges Bild) )
Anmerkung : in core.async, Kanal kann gebrauchte außerhalb von „go Block“ s, die ohne Park Fähigkeit durch eine JVM - Thread gesichert werden: zB wenn es blockiert, blockiert es die reale Faden.
Eine weitere große Sache in "Goroutines" / "Go Blocks" sind Operationen, die auf einem Kanal ausgeführt werden können. Beispielsweise kann ein Timeout-Kanal erstellt werden, der in X Millisekunden geschlossen wird. Oder wählen Sie / alt! Funktion, die in Verbindung mit vielen Kanälen wie ein "Bist du bereit" -Abfragemechanismus über verschiedene Kanäle hinweg funktioniert. Stellen Sie sich das als Socket-Selektor für nicht blockierende E / A vor. Hier ist ein Beispiel für die Verwendung von timeout channel
und alt!
zusammen:
(defn race [q]
(searching [:.yahoo :.google :.bing])
(let [t (timeout timeout-ms)
start (now)]
(go
(alt!
(GET (str "/yahoo?q=" q)) ([v] (winner :.yahoo v (took start)))
(GET (str "/bing?q=" q)) ([v] (winner :.bing v (took start)))
(GET (str "/google?q=" q)) ([v] (winner :.google v (took start)))
t ([v] (show-timeout timeout-ms))))))
Dieses Code-Snippet stammt aus dem Wracer und sendet dieselbe Anfrage an alle drei: Yahoo, Bing und Google. Es gibt ein Ergebnis vom schnellsten zurück oder es tritt eine Zeitüberschreitung auf (gibt eine Timeout-Nachricht zurück), wenn innerhalb einer bestimmten Zeit keine zurückgegeben wird. Clojure ist möglicherweise nicht Ihre Muttersprache, aber Sie können sich nicht darüber einig sein, wie sequentiell diese Implementierung von Parallelität aussieht und sich anfühlt.
Sie können auch / Fan-In / Fan-Out-Daten von / zu vielen Kanälen zusammenführen, Kanaldaten zuordnen / reduzieren / filtern / ... und vieles mehr. Kanäle sind auch erstklassige Bürger: Sie können einen Kanal an einen Kanal übergeben.
Da core.async "go blocks" diese Fähigkeit hat, den Ausführungsstatus zu "parken" und beim Umgang mit Parallelität ein sehr sequentielles "Look and Feel" zu haben, wie wäre es mit JavaScript? In JavaScript gibt es keine Parallelität, da es nur einen Thread gibt, oder? Die Art und Weise, wie Parallelität nachgeahmt wird, erfolgt über 1024 Rückrufe.
Das muss aber nicht so sein. Das obige Beispiel von Wracer ist tatsächlich in ClojureScript geschrieben, das bis zu JavaScript kompiliert wird. Ja, es funktioniert auf dem Server mit vielen Threads und / oder in einem Browser: Der Code kann gleich bleiben.
Wiederum einige Implementierungsunterschiede [es gibt mehr], um die Tatsache zu unterstreichen, dass das theoretische Konzept in der Praxis nicht genau eins zu eins ist:
Ich hoffe, dass das oben Gesagte etwas Licht auf die Unterschiede zwischen dem Schauspieler-Modell und CSP wirft.
Nicht um einen Flammenkrieg zu verursachen, sondern um Ihnen eine weitere Perspektive zu geben, sagen wir Rich Hickey:
" Ich bin immer noch nicht begeistert von Schauspielern. Sie koppeln den Produzenten immer noch mit dem Verbraucher. Ja, man kann bestimmte Arten von Warteschlangen mit Schauspielern emulieren oder implementieren (und insbesondere Menschen tun dies oft), aber da jeder Akteurmechanismus bereits eine Warteschlange enthält, ist dies der Fall." Es scheint offensichtlich, dass Warteschlangen primitiver sind. Es sollte beachtet werden, dass Clojures Mechanismen für die gleichzeitige Nutzung des Zustands weiterhin realisierbar sind und die Kanäle auf die Flussaspekte eines Systems ausgerichtet sind. "( Quelle )
In der Praxis basiert Whatsapp jedoch auf Erlang OTP und schien sich ziemlich gut zu verkaufen.
Ein weiteres interessantes Zitat stammt von Rob Pike:
" Gepufferte Sendungen werden dem Absender nicht bestätigt und können beliebig lange dauern. Gepufferte Kanäle und Goroutinen sind dem Akteurmodell sehr nahe.
Der wirkliche Unterschied zwischen dem Schauspielermodell und Go besteht darin, dass die Kanäle erstklassige Bürger sind. Ebenfalls wichtig: Sie sind indirekt wie Dateideskriptoren und nicht wie Dateinamen und ermöglichen Parallelitätsstile, die im Akteurmodell nicht so einfach ausgedrückt werden können. Es gibt auch Fälle, in denen das Gegenteil der Fall ist; Ich mache kein Werturteil. Theoretisch sind die Modelle äquivalent. "( Quelle )
Verschieben Sie einige meiner Kommentare zu einer Antwort. Es wurde zu lang: D (Nicht von Jamie und Tolitius 'Posts wegzunehmen; beide sind sehr nützliche Antworten.)
Es ist nicht ganz richtig, dass Sie genau die gleichen Dinge tun können, die Sie mit Goroutinen in Akka tun. Go-Kanäle werden häufig als Synchronisationspunkte verwendet. Sie können das nicht direkt in Akka reproduzieren. In Akka muss die Nachsynchronisierungsverarbeitung in einen separaten Handler verschoben werden (in Jamies Worten "verstreut": D). Ich würde sagen, die Designmuster sind unterschiedlich. Sie können eine Goroutine mit a starten, ein chan
paar Sachen machen und dann <-
warten, bis sie fertig ist, bevor Sie fortfahren . Akka hat eine weniger mächtige Form davon ask
, ist aber ask
nicht wirklich die Akka-Art IMO.
Chans werden ebenfalls eingegeben, Postfächer hingegen nicht. Das ist eine große Sache, IMO, und es ist ziemlich schockierend für ein Scala-basiertes System. Ich verstehe, dass dies become
mit getippten Nachrichten schwer zu implementieren ist, aber vielleicht deutet dies darauf hin, dass dies become
nicht sehr Scala-ähnlich ist. Ich könnte das allgemein über Akka sagen. Es fühlt sich oft wie ein eigenes Ding an, das zufällig auf Scala läuft. Goroutinen sind ein Hauptgrund, warum Go existiert.
Versteh mich nicht falsch; Ich mag das Schauspielermodell sehr, und ich mag Akka im Allgemeinen und finde es angenehm, darin zu arbeiten. Ich mag Go im Allgemeinen auch (ich finde Scala schön, während ich Go nur nützlich finde; aber es ist ziemlich nützlich).
Aber Fehlertoleranz ist wirklich der Punkt von Akka IMO. Sie bekommen zufällig Parallelität damit. Parallelität ist das Herzstück der Goroutinen. Fehlertoleranz ist eine separate Sache in Go, die an defer
und delegiert ist und recover
verwendet werden kann, um einiges an Fehlertoleranz zu implementieren. Akkas Fehlertoleranz ist formeller und funktionsreicher, kann aber auch etwas komplizierter sein.
Alle sagten, dass Akka trotz einiger vorübergehender Ähnlichkeiten keine Obermenge von Go ist und dass die Merkmale erheblich voneinander abweichen. Akka und Go unterscheiden sich sehr darin, wie sie Sie dazu ermutigen, Probleme anzugehen, und Dinge, die in einem einfach sind, in dem anderen umständlich, unpraktisch oder zumindest nicht idiomatisch. Und das ist das Hauptunterscheidungsmerkmal in jedem System.
Bringen Sie es also auf Ihre eigentliche Frage zurück: Ich würde dringend empfehlen, die Go-Oberfläche zu überdenken, bevor Sie sie zu Scala oder Akka bringen (was auch ganz andere Dinge sind, IMO). Stellen Sie sicher, dass Sie es so machen, wie es Ihre Zielumgebung bedeutet. Ein gerader Port einer komplizierten Go-Bibliothek passt wahrscheinlich nicht gut in beide Umgebungen.
Dies sind alles großartige und gründliche Antworten. Aber für eine einfache Sichtweise ist hier meine Ansicht. Goroutinen sind eine einfache Abstraktion von Schauspielern. Schauspieler sind nur ein spezifischerer Anwendungsfall von Goroutinen.
Sie können Schauspieler mithilfe von Goroutinen implementieren, indem Sie die Goroutine neben einem Kanal erstellen. Wenn Sie entscheiden, dass der Kanal dieser Goroutine gehört, sagen Sie, dass nur diese Goroutine davon konsumiert. Ihre Goroutine führt einfach eine Posteingangsnachrichten-Matching-Schleife auf diesem Kanal aus. Sie können den Kanal dann einfach als "Adresse" Ihres "Schauspielers" (Goroutine) weitergeben.
Da Goroutinen eine Abstraktion sind, ein allgemeineres Design als Schauspieler, können Goroutinen für weit mehr Aufgaben und Designs als Schauspieler verwendet werden.
Ein Nachteil ist jedoch, dass Implementierungen von Akteuren wie Erlang sie besser optimieren können (Schienenrekursion in der Posteingangsschleife) und andere integrierte Funktionen einfacher bereitstellen können (Multi-Prozess- und Maschinenakteure), da Akteure ein spezifischerer Fall sind. .
Können wir sagen, dass im Akteurmodell die adressierbare Entität der Akteur ist, der Empfänger der Nachricht? Während in Go-Kanälen die adressierbare Entität der Kanal ist, die Pipe, in der die Nachricht fließt.
Im Go-Kanal senden Sie eine Nachricht an den Kanal, und eine beliebige Anzahl von Empfängern kann zuhören, und einer von ihnen empfängt die Nachricht.
In Actor erhält nur ein Actor, an dessen Actor-Ref Sie die Nachricht senden, die Nachricht.