Ich habe eine Weile die wachsende Sichtbarkeit funktionaler Programmiersprachen und -funktionen beobachtet. Ich habe sie untersucht und den Grund für die Berufung nicht gesehen.
Dann habe ich kürzlich Kevin Smiths "Basics of Erlang" -Präsentation bei Codemash besucht .
Ich habe die Präsentation genossen und festgestellt, dass viele Attribute der funktionalen Programmierung es viel einfacher machen, Threading- / Parallelitätsprobleme zu vermeiden. Ich verstehe, dass der Mangel an Status und Veränderlichkeit es mehreren Threads unmöglich macht, dieselben Daten zu ändern, aber Kevin sagte (wenn ich es richtig verstanden habe), dass die gesamte Kommunikation über Nachrichten erfolgt und die Nachrichten synchron verarbeitet werden (wiederum um Parallelitätsprobleme zu vermeiden).
Aber ich habe gelesen, dass Erlang in hoch skalierbaren Anwendungen verwendet wird (der ganze Grund, warum Ericsson es überhaupt erstellt hat). Wie kann es effizient sein, Tausende von Anfragen pro Sekunde zu bearbeiten, wenn alles als synchron verarbeitete Nachricht behandelt wird? Ist das nicht der Grund, warum wir uns der asynchronen Verarbeitung zugewandt haben, damit wir mehrere Betriebsthreads gleichzeitig ausführen und Skalierbarkeit erreichen können? Diese Architektur scheint zwar sicherer zu sein, ist jedoch in Bezug auf die Skalierbarkeit ein Rückschritt. Was vermisse ich?
Ich verstehe, dass die Entwickler von Erlang absichtlich die Unterstützung von Threading vermieden haben, um Parallelitätsprobleme zu vermeiden, aber ich dachte, dass Multithreading notwendig ist, um Skalierbarkeit zu erreichen.
Wie können funktionale Programmiersprachen von Natur aus threadsicher und dennoch skalierbar sein?
quelle
Antworten:
Eine funktionale Sprache beruht (im Allgemeinen) nicht auf der Mutation einer Variablen. Aus diesem Grund müssen wir den "gemeinsamen Status" einer Variablen nicht schützen, da der Wert fest ist. Dies vermeidet wiederum den Großteil des Reifenspringens, den traditionelle Sprachen durchlaufen müssen, um einen Algorithmus prozessor- oder maschinenübergreifend zu implementieren.
Erlang geht noch weiter als herkömmliche funktionale Sprachen, indem es ein Nachrichtenübermittlungssystem einbaut, mit dem alles auf einem ereignisbasierten System ausgeführt werden kann, bei dem sich ein Code nur um das Empfangen und Senden von Nachrichten kümmert und sich nicht um ein größeres Bild kümmert.
Dies bedeutet, dass der Programmierer (nominell) nicht besorgt darüber ist, dass die Nachricht auf einem anderen Prozessor oder Computer verarbeitet wird: Das einfache Senden der Nachricht reicht aus, um fortzufahren. Wenn es um eine Antwort geht, wartet es als weitere Nachricht darauf .
Das Endergebnis davon ist, dass jedes Snippet unabhängig von jedem anderen Snippet ist. Kein gemeinsam genutzter Code, kein gemeinsam genutzter Status und alle Interaktionen, die von einem Nachrichtensystem stammen, das auf viele Hardwareteile verteilt werden kann (oder nicht).
Vergleichen Sie dies mit einem herkömmlichen System: Wir müssen Mutexe und Semaphoren um "geschützte" Variablen und die Codeausführung platzieren. Wir haben eine enge Bindung in einem Funktionsaufruf über den Stapel (warten auf die Rückkehr). All dies führt zu Engpässen, die in einem Shared-Nothing-System wie Erlang weniger problematisch sind.
EDIT: Ich sollte auch darauf hinweisen, dass Erlang asynchron ist. Sie senden Ihre Nachricht und vielleicht / eines Tages kommt eine andere Nachricht zurück. Oder nicht.
Spencers Argument bezüglich der Ausführung außerhalb der Reihenfolge ist ebenfalls wichtig und gut beantwortet.
quelle
Das Nachrichtenwarteschlangensystem ist cool, weil es effektiv einen "Feuer-und-Warten-auf-Ergebnis" -Effekt erzeugt, der der synchrone Teil ist, über den Sie lesen. Was dies unglaublich beeindruckend macht, ist, dass es bedeutet, dass Zeilen nicht nacheinander ausgeführt werden müssen. Betrachten Sie den folgenden Code:
Stellen Sie sich für einen Moment vor, dass die Fertigstellung von methodWithALotOfDiskProcessing () ungefähr 2 Sekunden und die Fertigstellung von methodWithALotOfNetworkProcessing () ungefähr 1 Sekunde dauert. In einer prozeduralen Sprache würde die Ausführung dieses Codes etwa 3 Sekunden dauern, da die Zeilen nacheinander ausgeführt würden. Wir verschwenden Zeit damit, darauf zu warten, dass eine Methode abgeschlossen wird, die gleichzeitig mit der anderen ausgeführt werden kann, ohne um eine einzelne Ressource zu konkurrieren. In einer funktionalen Sprache geben Codezeilen nicht vor, wann der Prozessor sie versuchen wird. Eine funktionale Sprache würde Folgendes versuchen:
Wie cool ist das? Indem wir mit dem Code fortfahren und nur bei Bedarf warten, haben wir die Wartezeit automatisch auf zwei Sekunden reduziert! : D Ja, obwohl der Code synchron ist, hat er tendenziell eine andere Bedeutung als in prozeduralen Sprachen.
BEARBEITEN:
Wenn Sie dieses Konzept in Verbindung mit Godekes Beitrag verstanden haben, können Sie sich leicht vorstellen, wie einfach es wird, mehrere Prozessoren, Serverfarmen, redundante Datenspeicher zu nutzen und wer weiß was noch.
quelle
Es ist wahrscheinlich, dass Sie synchron mit sequentiell verwechseln .
Der Körper einer Funktion in erlang wird sequentiell verarbeitet. Was Spencer über diesen "automagischen Effekt" sagte, gilt also nicht für erlang. Sie können dieses Verhalten jedoch mit erlang modellieren.
Sie können beispielsweise einen Prozess erzeugen, der die Anzahl der Wörter in einer Zeile berechnet. Da wir mehrere Zeilen haben, erzeugen wir für jede Zeile einen solchen Prozess und erhalten die Antworten, um daraus eine Summe zu berechnen.
Auf diese Weise erzeugen wir Prozesse, die die "schweren" Berechnungen durchführen (wobei zusätzliche Kerne verwendet werden, falls verfügbar), und sammeln später die Ergebnisse.
Und so sieht es aus, wenn wir dies in der Shell ausführen:
quelle
Der Schlüssel zur Skalierung von Erlang hängt mit der Parallelität zusammen.
Ein Betriebssystem bietet Parallelität durch zwei Mechanismen:
Prozesse teilen sich nicht den Status - ein Prozess kann einen anderen nicht beabsichtigt zum Absturz bringen.
Threads teilen sich den Status - ein Thread kann einen anderen von Natur aus zum Absturz bringen - das ist Ihr Problem.
Mit Erlang - ein Betriebssystemprozess wird von der virtuellen Maschine verwendet und die VM stellt dem Erlang-Programm Parallelität zur Verfügung, nicht durch Verwendung von Betriebssystem-Threads, sondern durch Bereitstellung von Erlang-Prozessen - das heißt, Erlang implementiert seinen eigenen Zeitschneider.
Diese Erlang-Prozesse kommunizieren miteinander, indem sie Nachrichten senden (die von der Erlang-VM und nicht vom Betriebssystem verarbeitet werden). Die Erlang-Prozesse adressieren sich gegenseitig mit einer Prozess-ID (PID), die eine dreiteilige Adresse hat
<<N3.N2.N1>>
:Zwei Prozesse auf derselben VM, auf verschiedenen VMs auf demselben Computer oder auf zwei Computern kommunizieren auf dieselbe Weise. Ihre Skalierung ist daher unabhängig von der Anzahl der physischen Computer, auf denen Sie Ihre Anwendung bereitstellen (in erster Näherung).
Erlang ist nur im trivialen Sinne threadsicher - es gibt keine Threads. (Die Sprache, dh die SMP / Multi-Core-VM, verwendet einen Betriebssystem-Thread pro Core.)
quelle
Möglicherweise haben Sie ein Missverständnis darüber, wie Erlang funktioniert. Die Erlang-Laufzeit minimiert das Kontextwechsel auf einer CPU. Wenn jedoch mehrere CPUs verfügbar sind, werden alle zur Verarbeitung von Nachrichten verwendet. Sie haben keine "Threads" in dem Sinne, wie Sie es in anderen Sprachen tun, aber Sie können viele Nachrichten gleichzeitig verarbeiten lassen.
quelle
Erlang-Nachrichten sind rein asynchron. Wenn Sie eine synchrone Antwort auf Ihre Nachricht wünschen, müssen Sie dies explizit codieren. Möglicherweise wurde gesagt, dass Nachrichten in einem Prozessnachrichtenfeld nacheinander verarbeitet werden. Jede an einen Prozess gesendete Nachricht befindet sich in diesem Prozessnachrichtenfeld, und der Prozess kann eine Nachricht aus diesem Feld auswählen und dann in der Reihenfolge, in der er dies für richtig hält, zur nächsten übergehen. Dies ist eine sehr sequentielle Handlung, und der Empfangsblock macht genau das.
Sieht so aus, als hätten Sie synchron und sequentiell verwechselt, wie Chris erwähnt hat.
quelle
Referenzielle Transparenz: Siehe http://en.wikipedia.org/wiki/Referential_transparency_(computer_science)
quelle
In einer rein funktionalen Sprache spielt die Reihenfolge der Auswertung keine Rolle - in einer Funktionsanwendung fn (arg1, .. argn) können die n Argumente parallel ausgewertet werden. Das garantiert ein hohes Maß an (automatischer) Parallelität.
Erlang verwendet ein Prozessmodell, bei dem ein Prozess in derselben virtuellen Maschine oder auf einem anderen Prozessor ausgeführt werden kann - es gibt keine Möglichkeit, dies festzustellen. Dies ist nur möglich, weil Nachrichten zwischen Prozessen kopiert werden und es keinen gemeinsamen (veränderlichen) Status gibt. Multiprozessor-Paralellismus geht viel weiter als Multithreading, da Threads vom gemeinsam genutzten Speicher abhängen. Auf einer 8-Kern-CPU können nur 8 Threads parallel ausgeführt werden, während Multi-Prozessor auf Tausende paralleler Prozesse skaliert werden kann.
quelle