Das Schauspielermodell: Warum ist Erlang / OTP etwas Besonderes? Könnten Sie eine andere Sprache verwenden?

77

Ich habe versucht, Erlang / OTP zu lernen, und als Ergebnis habe ich über das Schauspielermodell gelesen (okay, überfliegen).

Soweit ich weiß, besteht das Akteurmodell lediglich aus einer Reihe von Funktionen (die in einfachen Threads ausgeführt werden, die in Erlang / OTP als "Prozesse" bezeichnet werden), die nur über die Nachrichtenübermittlung miteinander kommunizieren .

Die Implementierung in C ++ oder einer anderen Sprache scheint ziemlich trivial zu sein:

class BaseActor {
    std::queue<BaseMessage*> messages;
    CriticalSection messagecs;
    BaseMessage* Pop();
public:
    void Push(BaseMessage* message)
    {
        auto scopedlock = messagecs.AquireScopedLock();
        messagecs.push(message);
    }
    virtual void ActorFn() = 0;
    virtual ~BaseActor() {} = 0;
}

Jeder Ihrer Prozesse ist eine Instanz eines abgeleiteten BaseActor. Akteure kommunizieren nur über die Weitergabe von Nachrichten miteinander. (nämlich schieben). Akteure registrieren sich bei der Initialisierung mit einer zentralen Karte, die es anderen Akteuren ermöglicht, sie zu finden, und ermöglicht es einer zentralen Funktion, sie zu durchlaufen.

Jetzt verstehe ich, dass ich hier ein wichtiges Thema vermisse oder vielmehr beschönige: Mangelnde Nachgiebigkeit bedeutet, dass ein einzelner Schauspieler zu viel Zeit zu Unrecht verbrauchen kann. Aber sind plattformübergreifende Coroutinen das Wichtigste, was dies in C ++ schwierig macht? (Windows hat zum Beispiel Fasern.)

Fehlt mir noch etwas oder ist das Modell wirklich so offensichtlich?

Jonathan Winks
quelle
5
Der Zweck einer Programmiersprache besteht darin, den Ausdruck einer Idee oder Spezifikation zu unterstützen. Das Darstellermodell ist in Erlang implizit enthalten. Während Sie Ihre Ideen in beiden Sprachen im Modell ausdrücken können, ist es in Erlang viel besser, da die Kesselplatte für Sie erstellt wird.
GManNickG
3
@ GMan, sobald die Kesselplatte fertig ist (es wäre ein einmaliger Gedanke, würde ich denken), was ist der Vorteil?
Seth Carnegie
4
@ SethCarnegie: Das ist in der Tat der Kern meiner Frage.
Jonathan Winks
15
erlang-Prozesse können sich auf demselben Computer oder auf verschiedenen physischen Computern befinden (und der tatsächliche Code, den Sie dazu schreiben, ist mehr oder weniger identisch), sodass Ihr Beispiel eine grobe Vereinfachung zu sein scheint. Und was ist mit Hot-Swapping-Code? Kann C ++ das auch einfach? Sind die Speicher Ihrer C ++ - Schauspieler im Sandkasten?
Kevin
20
Wenn Sie sicheren, zuverlässigen, gleichzeitigen und wartbaren Code in C ++ mit so wenig Aufwand wie in erlang implementieren können, fahren Sie fort. In diesem Ausschnitt fehlen jedoch Tonnen . Der Kern von erlang ist Zuverlässigkeit. Wenn ein Prozess nicht in der Lage ist, seine Aufgabe zu erfüllen, schlägt er fehl und diese Fehlermeldung wird über das System weitergegeben, sodass sich komplexe Abhängigkeitsgraphen bei verschiedenen Arten von Ausfällen (oder Fehlern) neu organisieren können. Sie können es tun, aber Sie sollten sich fragen, warum niemand es tut. Das führt zu neuen Sprachen.
Dustin

Antworten:

86

Der C ++ - Code befasst sich nicht mit Fairness, Isolation, Fehlererkennung oder -verteilung, die Erlang als Teil seines Akteurmodells mitbringt.

  • Kein Schauspieler darf einen anderen Schauspieler verhungern lassen (Fairness)
  • Wenn ein Schauspieler abstürzt, sollte dies nur diesen Schauspieler betreffen (Isolation)
  • Wenn ein Akteur abstürzt, sollten andere Akteure in der Lage sein, diesen Absturz zu erkennen und darauf zu reagieren (Fehlererkennung).
  • Akteure sollten in der Lage sein, über ein Netzwerk zu kommunizieren, als ob sie sich auf demselben Computer befinden (Verteilung)

Der Beam-SMP-Emulator bringt auch die JIT-Planung der Akteure und verschiebt sie in den Kern, der derzeit derjenige mit der geringsten Auslastung ist, und hält die Threads auf bestimmten Kernen in den Ruhezustand, wenn sie nicht mehr benötigt werden.

Darüber hinaus können alle in Erlang geschriebenen Bibliotheken und Tools davon ausgehen, dass die Welt so funktioniert, und entsprechend gestaltet werden.

Diese Dinge sind in C ++ nicht unmöglich zu tun, aber sie werden immer schwieriger, wenn man hinzufügt, dass Erlang auf fast allen wichtigen Hardware- und Betriebssystemkonfigurationen funktioniert.

edit: Habe gerade eine Beschreibung von Ulf Wiger gefunden, als was er Erlang-Stil-Parallelität sieht.

Lukas
quelle
1
Ich würde definitiv Prozessisolation und Fehlerbehandlung in das erlang-Parallelitätsmodell aufnehmen, sonst ist das, was Ulf schreibt, sehr gut.
Rvirding
5
Alle von Ihnen aufgelisteten Eigenschaften werden vom Betriebssystem für Prozesse bereitgestellt. C ++ - Programme können sie wie jedes andere Programm problemlos verwenden. Ich denke, der Schlüssel zu Erlang ist, dass seine Akteure für die Bereitstellung dieser Eigenschaften weitaus billiger sind als Betriebssystemprozesse. Dadurch können Schauspieler freier eingesetzt werden.
Karmastan
3
@Karmastan Ja, Erlang-Prozesse sind sehr billig, da Parallelität die grundlegende Abstraktion bei der Strukturierung von Anwendungen ist. Wir nennen sie lieber Prozesse als Schauspieler, wir hatten bei der Gestaltung von Erlang noch nichts von Schauspielern gehört. :-)
rvirding
30

Ich zitiere mich nicht gern, sondern aus Virdings erster Programmierregel

Jedes ausreichend komplizierte gleichzeitige Programm in einer anderen Sprache enthält eine informell spezifizierte, fehlerbehaftete Ad-hoc-Implementierung der Hälfte von Erlang.

In Bezug auf Greenspun. Joe (Armstrong) hat eine ähnliche Regel.

Das Problem ist nicht, Akteure zu implementieren, das ist nicht so schwierig. Das Problem besteht darin, dass alles zusammenarbeitet: Prozesse, Kommunikation, Speicherbereinigung, Sprachprimitive, Fehlerbehandlung usw. Die Verwendung von Betriebssystem-Threads lässt sich beispielsweise schlecht skalieren, sodass Sie dies selbst tun müssen. Es wäre, als würde man versuchen, eine OO-Sprache zu "verkaufen", in der man nur 1k Objekte haben kann und deren Erstellung und Verwendung schwer ist. Parallelität ist aus unserer Sicht die grundlegende Abstraktion für die Strukturierung von Anwendungen.

Ich werde mitgerissen, damit ich hier aufhöre.

rvirding
quelle
"also werde ich hier aufhören": mehr wäre interessant gewesen
serv-inc
22

Dies ist eigentlich eine ausgezeichnete Frage und hat ausgezeichnete Antworten erhalten, die vielleicht noch nicht überzeugend sind.

So fügen Sie Schatten und die Betonung auf die anderen großen Antworten schon hier, überlegt , was Erlang wegnimmt ( im Vergleich zu herkömmlichen Allzweck - Sprachen wie C / C ++) , um Fehlertoleranz und Verfügbarkeit zu erreichen.

Erstens nimmt es Schlösser weg. In Joe Armstrongs Buch wird dieses Gedankenexperiment beschrieben: Angenommen, Ihr Prozess erhält eine Sperre und stürzt dann sofort ab (ein Speicherfehler führt dazu, dass der Prozess abstürzt oder die Stromversorgung eines Teils des Systems ausfällt). Wenn ein Prozess das nächste Mal auf dieselbe Sperre wartet, ist das System gerade blockiert. Dies könnte eine offensichtliche Sperre sein, wie beim Aufruf von AquireScopedLock () im Beispielcode. oder es könnte eine implizite Sperre sein, die in Ihrem Namen von einem Speichermanager erworben wurde, beispielsweise beim Aufruf von malloc () oder free ().

In jedem Fall hat Ihr Prozessabsturz jetzt das gesamte System daran gehindert, Fortschritte zu erzielen. Fini. Ende der Geschichte. Ihr System ist tot. Sofern Sie nicht garantieren können, dass jede in C / C ++ verwendete Bibliothek niemals malloc aufruft und niemals eine Sperre erhält, ist Ihr System nicht fehlertolerant. Erlang-Systeme können Prozesse unter hoher Last nach Belieben beenden, um Fortschritte zu erzielen. Daher müssen Ihre Erlang-Prozesse im Maßstab (an jedem einzelnen Ausführungspunkt) tötbar sein, um den Durchsatz aufrechtzuerhalten.

Es gibt eine teilweise Problemumgehung: Verwenden Sie Leases überall anstelle von Sperren. Sie können jedoch nicht garantieren, dass alle von Ihnen verwendeten Bibliotheken dies auch tun. Und die Logik und Argumentation über Korrektheit wird sehr schnell haarig. Darüber hinaus erholen sich Leases langsam (nach Ablauf des Timeouts), sodass Ihr gesamtes System angesichts eines Ausfalls nur sehr langsam wird.

Zweitens beseitigt Erlang die statische Typisierung, was wiederum das gleichzeitige Austauschen und Ausführen von zwei Versionen desselben Codes ermöglicht. Dies bedeutet, dass Sie Ihren Code zur Laufzeit aktualisieren können, ohne das System anzuhalten. Auf diese Weise bleiben die Systeme neun Neun oder 32 ms Ausfallzeit pro Jahr in Betrieb. Sie werden einfach an Ort und Stelle aktualisiert. Ihre C ++ - Funktionen müssen manuell neu verknüpft werden, um ein Upgrade durchzuführen, und die gleichzeitige Ausführung von zwei Versionen wird nicht unterstützt. Code-Upgrades erfordern Systemausfallzeiten. Wenn Sie einen großen Cluster haben, in dem nicht mehr als eine Codeversion gleichzeitig ausgeführt werden kann, müssen Sie den gesamten Cluster auf einmal herunterfahren. Autsch. Und in der Telekommunikationswelt nicht erträglich.

Außerdem nimmt Erlang gemeinsam genutzten Speicher und gemeinsam genutzte Speicherbereinigung weg. Jeder leichte Prozess ist Müll, der unabhängig gesammelt wird. Dies ist eine einfache Erweiterung des ersten Punkts, betont jedoch, dass Sie für eine echte Fehlertoleranz Prozesse benötigen, die nicht in Bezug auf Abhängigkeiten miteinander verbunden sind. Dies bedeutet, dass Ihre GC-Pausen im Vergleich zu Java für große Systeme tolerierbar sind (klein, anstatt eine halbe Stunde anzuhalten, damit ein 8-GB-GC abgeschlossen ist).

jaten
quelle
1
Erstens können Sie lock_guard verwenden, das Ihre Sperre aufhebt, falls das Programm abstürzt. Zweitens können Sie ein Hot-Swap-System in C ++ implementieren, aber es ist ein Problem ... Das Problem bei der Parallelität besteht darin, dass Synchronisationsprimitive, sogar Atomics, Speicherzäune und -barrieren einführen und langsamer werden. Je mehr Threads Sie haben, desto langsamer werden Sie. Erlang verwendet als Clojure oder Haskell keine Mutexe oder Atomics, was den Entwickler dazu zwingt, das Problem auf andere Weise festzulegen. Dies ist eine sehr effiziente Methode zur Lösung von Parallelitätsproblemen
Asier Gutierrez
Klingt gültig, aber das ist nur ein C ++ - Vergleich und C ++ ist immer ein leicht zu beschuldigendes Ziel. Ist es nicht möglich, dies zum Beispiel in Java (oder Clojure) zu haben? Sperren in Java sind sicher und es gibt Möglichkeiten, Code zur Laufzeit zu kompilieren / zu laden (in Clojure ist dies auch sehr einfach).
Anzeigename
3

Es geht viel weniger um das Schauspieler-Modell als vielmehr darum, wie schwierig es ist, etwas Analoges zu OTP in C ++ richtig zu schreiben. Außerdem bieten verschiedene Betriebssysteme radikal unterschiedliche Debugging- und System-Tools, und Erlangs VM und verschiedene Sprachkonstrukte unterstützen eine einheitliche Methode, um herauszufinden, was all diese Prozesse vorhaben, was auf einheitliche Weise sehr schwierig (oder vielleicht auch schwierig) wäre überhaupt) über mehrere Plattformen hinweg. (Es ist wichtig, sich daran zu erinnern, dass Erlang / OTP vor dem aktuellen Trend über den Begriff "Akteurmodell" liegt. In einigen Fällen werden bei solchen Diskussionen Äpfel und Pterodaktylen verglichen. Großartige Ideen sind anfällig für unabhängige Erfindungen.)

All dies bedeutet, dass Sie sicherlich eine "Schauspieler-Modell" -Suite von Programmen in einer anderen Sprache schreiben können (ich weiß, ich habe dies in Python, C und Guile lange Zeit getan, ohne es zu merken, bevor ich auf Erlang gestoßen bin, einschließlich einer Form von Monitore und Links, und bevor ich jemals den Begriff "Akteurmodell" gehört hatte), zu verstehen, wie die Prozesse, die Ihr Code tatsächlich erzeugt, und was zwischen ihnen geschiehtist extrem schwierig. Erlang setzt Regeln durch, die ein Betriebssystem ohne größere Kernelüberholungen einfach nicht kann - Kernelüberholungen, die insgesamt wahrscheinlich nicht vorteilhaft wären. Diese Regeln äußern sich sowohl in allgemeinen Einschränkungen für den Programmierer (die immer umgangen werden können, wenn Sie es wirklich brauchen) als auch in grundlegenden Versprechungen, die das System dem Programmierer garantiert (die absichtlich gebrochen werden können, wenn Sie es wirklich brauchen).

Beispielsweise wird erzwungen, dass zwei Prozesse den Status nicht gemeinsam nutzen können, um Sie vor Nebenwirkungen zu schützen. Dies bedeutet nicht, dass jede Funktion in dem Sinne "rein" sein muss, dass alles referenziell transparent ist (offensichtlich nicht, obwohl es ein klares Entwurfsziel der meisten Erlang-Projekte ist, so viel von Ihrem Programm referenziell transparent wie praktisch zu machen), sondern dass zwei Prozesse schaffen nicht ständig Rennbedingungen, die sich auf den gemeinsamen Zustand oder die Konkurrenz beziehen. (Dies ist übrigens eher das, was "Nebenwirkungen" im Kontext von Erlang bedeuten. Wenn Sie wissen, dass dies Ihnen helfen kann, einen Teil der Diskussion zu entschlüsseln, in der Sie sich fragen, ob Erlang im Vergleich zu "reinen" Haskell- oder Spielzeugsprachen "wirklich funktionsfähig oder nicht" ist .)

Andererseits garantiert die Erlang-Laufzeit die Zustellung von Nachrichten. Dies wird in einer Umgebung, in der Sie nur über nicht verwaltete Ports, Pipes, gemeinsam genutzten Speicher und allgemeine Dateien kommunizieren müssen, die nur vom Betriebssystemkern verwaltet werden, schmerzlich übersehen (und die Verwaltung dieser Ressourcen im Betriebssystemkern ist im Vergleich zu Erlang notwendigerweise äußerst gering Laufzeit bietet). Dies bedeutet nicht, dass Erlang RPC garantiert (ohnehin ist die Nachrichtenübergabe weder RPC noch ein Methodenaufruf!), Es verspricht nicht, dass Ihre Nachricht korrekt adressiert ist, und es verspricht nicht, dass Sie ein Prozess sind Der Versuch, eine Nachricht an exist zu senden oder lebt. Es garantiert nur die Lieferung, wenn die Sache, an die Sie senden, gerade gültig ist.

Aufbauend auf diesem Versprechen ist das Versprechen, dass Monitore und Links korrekt sind. Und basierend darauf lässt die Erlang-Laufzeit das gesamte Konzept des "Netzwerkclusters" irgendwie dahinschmelzen, sobald Sie verstehen, was mit dem System los ist (und wie erl_connect verwendet wird ...). Auf diese Weise können Sie bereits über eine Reihe kniffliger Fälle von Parallelität springen, was Ihnen einen großen Vorsprung bei der Codierung des erfolgreichen Falls verschafft, anstatt sich in den Sumpf von Abwehrtechniken zu verstricken, die für nackte gleichzeitige Programmierung erforderlich sind.

Es geht also nicht wirklich darum , Erlang, die Sprache, die Laufzeit und das bereits vorhandene OTP zu benötigen , auf ziemlich saubere Weise ausgedrückt zu werden und alles, was ihm nahe steht, in einer anderen Sprache zu implementieren, was äußerst schwierig ist. OTP ist nur schwer zu befolgen. Ebenso brauchen wir C ++ auch nicht wirklich , wir könnten uns einfach an die rohe Binäreingabe Brainfuck halten und Assembler als unsere Hochsprache betrachten. Wir brauchen auch keine Züge oder Schiffe, da wir alle laufen und schwimmen können.

Trotzdem ist der Bytecode der VM gut dokumentiert, und es sind eine Reihe alternativer Sprachen entstanden, die kompiliert werden oder mit der Erlang-Laufzeit arbeiten. Wenn wir die Frage in einen Sprach- / Syntaxteil ("Muss ich Moon Runes verstehen, um Parallelität zu betreiben?") Und einen Plattformteil ("Ist OTP die ausgereifteste Methode, um Parallelität zu betreiben, aufteilen) und wird es mich durch die schwierigsten führen , die häufigsten Fallstricke in einer gleichzeitig verteilten Umgebung? ") Dann lautet die Antwort (" nein "," ja ").

zxq9
quelle
2

Casablanca ist ein weiteres neues Kind im Schauspieler-Modellblock. Eine typische asynchrone Annahme sieht folgendermaßen aus:

PID replyTo;
NameQuery request;
accept_request().then([=](std::tuple<NameQuery,PID> request)
{
   if (std::get<0>(request) == FirstName)
       std::get<1>(request).send("Niklas");
   else
       std::get<1>(request).send("Gustafsson");
}

(Ich persönlich finde, dass CAF die Musterübereinstimmung besser hinter einer schönen Oberfläche versteckt.)

Mavam
quelle