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?
quelle
Antworten:
Der C ++ - Code befasst sich nicht mit Fairness, Isolation, Fehlererkennung oder -verteilung, die Erlang als Teil seines Akteurmodells mitbringt.
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.
quelle
Ich zitiere mich nicht gern, sondern aus Virdings erster Programmierregel
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.
quelle
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).
quelle
Es gibt aktuelle Akteursbibliotheken für C ++:
http://actor-framework.org/
http://www.theron-library.com/
Und eine Liste einiger Bibliotheken für andere Sprachen.
quelle
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 ").
quelle
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.)
quelle