Nebenläufigkeit ganz einfach mit Elixir und Erlang

Moore's Law – also die Verdoppelung der Rechenleistung von CPUs alle 12 bis 24 Monate – wird bald an sein Ende gelangen. Schon jetzt zeichnet sich ab, dass der einzelne Rechenkern nicht mehr signifikant schneller werden wird. Stattdessen geht der Trend dazu, mehr Kerne gleichzeitig zu verwenden. Dies wird durch die Möglichkeiten der Cloud, Systeme fast beliebig zu skalieren, noch verstärkt. Das bedeutet, dass Entwickler ihre Programme darauf auslegen müssen, diese neue Hardware-Realität möglichst gut zu nutzen.
In vielen gängigen Programmiersprachen ist es aber nicht so einfach, stabile und fehlerfreie Software zu schreiben, die wirklich alle zur Verfügung stehenden CPUs nutzt. Erlang und die darauf aufbauende funktionale Programmiersprache Elixir sind dagegen von Anfang an darauf ausgelegt, skalierbare, fehlertolerante Systeme zu entwickeln. Dies erleichtert die Entwicklung eines nebenläufigen Programms enorm.
Parallelisierung vs. Nebenläufigkeit
Es ist wichtig, die beiden Arten, Mehrkern-Systeme auszunutzen, zu unterscheiden:
- Einerseits gibt es hier die Parallelisierung: Das gleichzeitige Anwenden der gleichen Funktionen auf Datenpakete, die einer aufgeteilten größeren Datenmenge entstammen. Dies benötigt allerdings eine parallelisierbare Aufgabenstellung, zum Beispiel eine Berechnung, die nicht auf einen gemeinsamen Zustand – der dann ständig synchronisiert werden müsste – zwischen den einzelnen Abarbeitungs-Funktionen der jeweiligen Pakete angewiesen ist.
- Im Gegensatz dazu steht die Nebenläufigkeit. Dabei werden unterschiedliche Funktionen auf unterschiedliche Daten angewendet. Die einzelnen Verarbeitungsstränge haben dabei oft keinerlei Beziehung zueinander. Ein häufig anzutreffendes Beispiel dafür ist ein Webserver: Jede eingehende Anfrage sollte möglichst verzögerungsfrei bearbeitet werden, hängt normalerweise aber nicht mit anderen, gleichzeitig eintreffenden Anfragen zusammen. Es macht daher Sinn, jede Anfrage in einem eigenen Programm-Thread zu bearbeiten, um die vorhandene Rechenleistung möglichst gut auszunutzen und die Antwortzeiten zu minimieren.
In diesem Artikel geht es ausschließlich um Nebenläufigkeit. Erlang und Elixir bieten aber auch zahlreiche Möglichkeiten zur Parallelisierung von Berechnungen.
Erlang
Die Programmiersprache Erlang [1] wurde in den 90er Jahren bei Ericsson entwickelt. Ziel war dabei eine Programmiersprache, die eine einfache Programmierung von nebenläufigen, skalierbaren und fehlertoleranten Systemen ermöglicht. Haupteinsatzbereich war dabei die Verwendung in Telekommunikationssystemen, bei denen jegliche Downtime absolut unerwünscht ist. Die ursprüngliche Herkunft lässt sich auch daran erkennen, dass viele Begriffe und Bezeichnungen innerhalb von Sprache und umgebendem Ökosystem sehr auf den Telekommunikationsbereich bezogen sind. Zumeist wird mit Erlang auch nicht nur die eigentliche Sprache gemeint, sondern auch die zugehörige Programmbaustein-Bibliothek OTP ("Open Telecom Platform"), auf die später noch genauer eingegangen wird.
Das Aktor-Modell
Erlang-Systeme beruhen dabei immer auf einer Systemarchitektur, die auf dem Aktor-Modell aufbaut. Laut eigener Aussage der Erlang-Entwickler kannten diese das Aktor-Modell zwar nicht, kamen aber bei ihren Versuchen, eine Programmiersprache samt zugehöriger Standard-Architektur zu erschaffen, die sich für die von ihnen geplanten Zwecke eignet, zum gleichen Ergebnis. Sie sprechen daher von einer "zufälligen Implementierung" des Aktor-Modells.
Das Grundprinzip des Aktor-Modells ist einfach: Es gibt voneinander völlig unabhängige Aktoren. Diese können einen inneren Zustand besitzen. Dieser Zustand ist von außen nicht einseh- oder gar veränderbar. Dies wird auch "shared nothing" bezeichnet. Die Kommunikation mit anderen Aktoren funktioniert ausschließlich nachrichtenbasiert: Jeder Aktor besitzt eine Eingangswarteschlange und wartet passiv auf eingehende Nachrichten. Sobald eine Nachricht eintrifft, wird diese verarbeitet. Die Verarbeitung im Aktor passiert dabei immer sequentiell – es wird genau eine Nachricht gleichzeitig verarbeitet. Sobald die Verarbeitung abgeschlossen ist, wird die nächste Nachricht aus der Eingangswarteschlange entnommen und verarbeitet oder – falls die Warteschlange leer ist – wieder gewartet. Das Gesamtsystem besteht aus einer Vielzahl von Aktoren. Je nach Systemgröße und Anforderungen können hier schnell hunderte oder gar tausende Aktoren aktiv sein. Da die Aktoren keinen geteilten Zustand haben, kann die Nachrichtenverarbeitung jedes Aktors auf einer anderen CPU erledigt werden – dadurch lassen sich fast beliebig viele Rechenkerne gleichzeitig auslasten. Weil die Verarbeitung im Aktor sequentiell abläuft, kann der eigene Programmcode ohne Rücksicht auf Synchronisierung, Thread-Management oder andere Herausforderungen der nebenläufigen Programmierung geschrieben werden.
Prozesse
In Erlang werden Aktoren allerdings nicht Aktoren genannt, sondern Prozesse. Beide Begriffe werden im nachfolgenden Text austauschbar verwendet. Mit Prozess sind aber keine schwergewichtigen Betriebssystem-Prozesse gemeint, deren Erzeugung recht teuer ist und die einiges an Arbeitsspeicher belegen. Stattdessen sind Erlang-Prozesse mit ca. 1 KB RAM pro erzeugtem Prozess leichtgewichtig und können sehr schnell – innerhalb weniger Mikrosekunden – erzeugt und auch wieder zerstört werden. Deshalb wird in Erlang-Systemen geradezu verschwenderisch mit Prozessen umgegangen: Der innerhalb der Erlang-Community sehr häufig verwendete Webserver "Cowboy" erzeugt zum Beispiel pro eintreffender Anfrage einen Prozess. In diesem Prozess wird die Anfrage bearbeitet, und nach dem Versand der Antwort wird der Prozess wieder zerstört. Dies hat den Nebeneffekt, dass eine Garbage Collection oft gar nicht zur Laufzeit eines Prozesses stattfindet. Stattdessen wird der Speicher einfach freigegeben, nachdem der Prozess zerstört wurde. Dies verhindert das in anderen Systemen immer wieder zu beobachtende Verhalten, dass während einer globalen Garbage Collection das System kurzzeitig deutlich längere Antwortzeiten zeigt.
Die Prozesse werden dann von der Erlang VM, die auch BEAM genannt wird, auf die zur Verfügung stehenden Rechenkerne verteilt. Standardmäßig wird dazu ein Betriebssystem-Prozess gestartet, in dem wiederum pro CPU ein Thread erzeugt wird. Diesem werden dann die einzelnen Erlang-Prozesse so zugeordnet, dass alle Kerne möglichst gleichmäßig ausgelastet werden. Aus Entwicklersicht läuft das alles transparent ab - im Normalfall ist ein aktives Management dieses Verhaltens daher nicht nötig.
Eine weitere wichtige Besonderheit der Erlang-Runtime, die diese deutlich von praktisch allen anderen Mainstream-Runtimes wie der JVM- oder der .NET-Runtime unterscheidet, ist der Umgang mit den einzelnen zu bearbeitenden "Rechenpaketen". In den meisten Runtimes werden Berechnungen nur dann unterbrochen, wenn der jeweilige Programmcode explizit eine Möglichkeit dafür vorsieht. Der zu schreibende Code muss also mit der Runtime aktiv kooperieren, daher wird diese Art der Rechenzeitverteilung durch die Runtime auch "cooperative scheduler" genannt.
In Abb. 3 wird ein Problem damit deutlich: Sobald eine einzelne Berechnung eine CPU für längere Zeit besetzt, kommen andere Berechnungen nicht zum Zug. Als Beispiel kann hier eine Webseite dienen, die einerseits eine einfache Login/Logout-Funktionalität mit einer simplen Formular-Eingabemaske bereitstellt, andererseits aber auch eine Simulationsoberfläche, bei der komplexe Formeln eingegeben und anschließend aufwändig server-seitig ausgerechnet werden.
Steht nun nur eine begrenzte Anzahl an Rechenkernen zur Verfügung, können potentiell alle Kerne irgendwann von Simulationsberechnungen blockiert sein – und falls diese sich nicht kooperativ verhalten, klappt in so einem Fall nicht mal mehr ein einfacher Login, und auch eine Eingabe von Daten über ein Formular müsste so lange auf eine Antwort warten, bis irgendein Rechenkern wieder freigegeben würde. Eine solche Verzögerung ist unerwünscht.
Der Erlang-Scheduler hingegen funktioniert wie gängige Betriebssystem-Scheduler: Es handelt sich um einen "preemptive scheduler", der jeden Erlang-Prozess jederzeit unterbrechen kann, um anderen Prozessen Rechenzeit zukommen zu lassen. In obigem Beispiel würden daher Login/Logout und die Eingabemaske mit praktisch unveränderter Antwortzeit weiterhin funktionieren, auch während eigentlich alle CPUs mit Berechnungen ausgelastet sind. Dieses Laufzeitverhalten der Erlang-Runtime hat den zusätzlichen Vorteil, dass auch Server mit nur geringen Hardwareressourcen bereits sehr gut für Erlang-Anwendungen verwendet werden können, da selbst Systeme mit nur einer CPU auch bei vielen gleichzeitigen Benutzern jederzeit gleichbleibend gute Antwortzeiten bieten.
Elixir
Die Syntax von Erlang ist von Prolog inspiriert und daher durchaus gewöhnungsbedürftig. Praktischerweise gibt es aber mit Elixir [2] eine auf Erlang aufsetzende moderne Programmiersprache, deren Syntax für Entwickler aus dem C/C++/Java/.NET-Umfeld leichter verdaulich ist. Elixir ist eine dynamisch typisierte, funktionale Programmiersprache, die vom Elixir-Compiler zu Erlang-Bytecode kompiliert und anschließend in der Erlang VM ausgeführt wird. Dabei sind die Sprachen voll kompatibel zueinander: Alles, was in Erlang ausgedrückt werden kann, kann auch in Elixir ausgedrückt werden und umgekehrt. Auch kann Code der jeweils einen Sprache problemlos in der anderen aufgerufen werden.
Elixir ist noch jung: Version 1.0 ist im Herbst 2014 erschienen. Dadurch konnten viele Herausforderungen der modernen Softwareentwicklung von Anfang an im Sprach- und Tool-Design berücksichtigt werden. Bei älteren Programmiersprachen müssen entsprechende Anpassungen nachträglich, oft unter Schwierigkeiten, eingefügt werden. Die Sprachsyntax orientiert sich stark an Ruby – der Hauptentwickler Elixirs, José Valim, war vorher Teil des Ruby-Core-Teams. Es gibt bereits ein großes Ökosystem mit vielen Bibliotheken, die zumeist aktiv gepflegt werden und eine hervorragende Dokumentation haben.
Die Einsatz-Schwerpunkte von Elixir und Erlang sind dabei Systeme mit niedriger Latenz, hoher Skalierbarkeit und Fehlertoleranz. Durch den Ursprung Erlangs in der Telekommunikationsindustrie können beide Sprachen auch sehr gut mit bit-basierten Protokollen umgehen. Außerdem gibt es mittlerweile Projekte, die die Entwicklung von interaktiven Webseiten in Elixir ermöglichen (LiveView [3]), das Umsetzen von Desktopanwendungen (Scenic [4]) oder auch den Einsatz auf Embedded Hardware (Nerves [5]). Auch für die Entwicklung von webbasierten Diensten eignet sich Elixir durch das Phoenix-Projekt [6] hervorragend.
OTP
Eine Programmiersprache alleine reicht noch nicht aus, um damit effizient größere Softwaresysteme zu bauen. Wichtig ist dafür auch eine umfangreiche Standardbibliothek. Zu Erlang gehört daher fest OTP, die Open Telecom Platform. Damit können allerdings nicht nur Telekommunikationssysteme entwickelt werden, auch wenn das der ursprüngliche Einsatzzweck war. OTP stellt eine Sammlung direkt einsatzbereiter Abstraktionen bereit, die häufige Aufgabenstellungen in aktor-modell-basierenden Systemen stark vereinfachen. Im Folgenden werden die wichtigsten davon vorgestellt: Tasks, Agents, GenServers und Supervisors.
Tasks dienen dazu, eine Funktion einmalig in einem neuen Erlang-Prozess aufzurufen. Die Syntax dazu sieht ähnlich aus wie in anderen Programmiersprachen, die async/await unterstützen:
def task_syntax_demo() do
task = Task.async(fn -> long_running_calculation() end)
do_something_else()
task_result = Task.await(task)
end
Mit Agents wird innerhalb eines Erlang-Prozesses ein Zustand verwaltet, ohne weitere Funktionalität anzubieten. Ein Agent ist daher in etwa mit einem in einen eigenen Erlang-Prozess ausgelagerten Dictionary zu vergleichen, das von verschiedenen anderen Aktoren benutzt werden kann. Agents bieten eine genau festgelegte Schnittstelle nach außen, da die einzigen erlaubten Funktionen solche sind, die den abgelegten Zustand auslesen oder setzen.
Daten sind in Elixir immer unveränderlich ("immutable"), daher muss für Veränderungen immer eine Kopie des Zustands mit geänderten Werten erzeugt werden. Dies verhindert das versehentliche Verändern von Daten, auf die sich andere Komponenten vielleicht verlassen. Da außerdem Nachrichten, die an einen Aktor geschickt werden, immer nacheinander abgearbeitet werden, kann es innerhalb eines Agents keine Race Conditions geben – jede Veränderung des Zustands ist also gut nachvollziehbar.
In Elixir-Code sieht ein kleiner Beispiel-Agent so aus:
defmodule SimpleCounter do
use Agent
# Durch die Vergabe eines "name" kann später einfacher wieder auf den Agent zugegriffen werden
# __MODULE__ ist hierbei ein Makro, das automatisch durch den Modulnamen ersetzt wird
def start_link(initial_value) do
Agent.start_link(fn -> initial_value end, name: __MODULE__)
end
def value do
Agent.get(__MODULE__, fn val -> val end)
end
def increment do
Agent.update(__MODULE__, fn val -> val + 1 end)
end
end
GenServer ("Generic Server") bilden das Herz jeder Elixir-Applikation. Sie kombinieren einen internen Zustand mit beliebiger Funktionalität. Jeder GenServer ist dabei ein eigener, von allen anderen völlig unabhängiger Erlang-Prozess. Untereinander kommunizieren die GenServer nur über Nachrichten miteinander.
Im Inneren jedes GenServers steckt eigentlich eine Endlosschleife: Warten auf neue Nachrichten, verarbeiten der Nachrichten, ggf. eine Antwort schicken, eigenen Zustand aktualisieren, und dann wieder das Warten auf neue Nachrichten. Im Normalfall ist eine direkte Auseinandersetzung mit dieser Schleife nicht notwendig, da die GenServer-Abstraktion die Details verbirgt.
Da das direkte Empfangen und Versenden von Nachrichten mühselig werden kann, werden GenServer in der Praxis immer in zwei Teile aufgeteilt: Nach außen hin sichtbar sind einfache Funktionen, die vom verwendenden Code direkt aufgerufen werden können. Dadurch wird die nachrichtenbasierte Natur verborgen. Diese Funktionen allerdings senden dann jeweils eine Nachricht an den eigentlichen GenServer-Prozess. Bei genauer Betrachung ist es daher so, dass der Funktionsaufruf noch im Kontext des aufrufenden Aktors ausgeführt wird, die Verarbeitung der Nachricht dann aber im tatsächlichen GenServer-Aktor.
Es gibt zwei verschiedene Arten der Nachrichten-Kommunikation: "Calls" sind blockierende Aufrufe, bei denen auf die Antwort gewartet wird. Ein ausschließlich aus Calls bestehendes System hat ein leicht nachvollziehbares Verhalten und lässt sich einfach programmieren, skaliert allerdings nicht unbedingt besonders gut, da ein Prozess, der auf eine Antwort eines anderen Prozesses wartet, solange blockiert ist und keine weiteren Nachrichten verarbeitet. Im Gegensatz dazu sind "Casts" nicht-blockierend, also "Fire and Forget". Entweder es wird gar keine Antwort erwartet oder der Aufrufer muss seine eigene sogenannte Prozess-ID mitschicken, damit die Gegenseite nach Verarbeitung der Nachricht weiß, an welchen Prozess die Antwort geschickt werden muss. Derlei konstruierte Systeme skalieren sehr gut, allerdings muss bei der Programmierung immer auf die Asynchronität aller Vorgänge geachtet werden.
Zur Illustration eines einfachen GenServers kann hierbei ein Beispiel aus der offiziellen Elixir-Dokumentation [7] dienen, bei dem ein Stack implementiert wird:
defmodule Stack do
use GenServer
# Verwaltungsfunktionen, die zur Einbindung in ein Gesamtsystem notwendig sind
def start_link(default) do
GenServer.start_link(__MODULE__, default)
end
def init(stack) do
{:ok, stack}
end
# Client - einfache Funktionen, die Nachrichten auslösen
# "pid" ist die Prozess-ID des angesprochenen GenServers
def push(pid, element) do
# Cast ist "Fire and Forget"
GenServer.cast(pid, {:push, element})
end
def pop(pid) do
# Ein Call blockiert, bis die Antwort da ist
GenServer.call(pid, :pop)
end
# Server - Callbacks zur Nachrichtenbehandlung
def handle_call(:pop, _from, [head | tail]) do
{:reply, head, tail}
end
def handle_cast({:push, element}, state) do
{:noreply, [element | state]}
end
end
Supervisors
Arbeits-Prozesse alleine reichen aber nicht aus, um ein vollständiges Erlang-System zu bauen, denn ein wichtiger Teil eines solchen Systems ist das Verwalten des Lebenszyklus der einzelnen Prozesse: In welcher Reihenfolge müssen diese gestartet werden? In welcher müssen sie beim geordneten Beenden des Systems gestoppt werden? Was passiert, wenn ein Prozess unerwartet beendet wird? All diese Fragen werden in Erlang, und damit auch in Elixir, mit Aufseher-Prozessen geregelt. Diese Supervisor-Prozesse sind dabei immer in eine Baumstruktur eingebunden. Einzig der oberste Aufseher – die Wurzel des Baumes – hat keine weiteren Prozesse mehr über sich. Die Gliederung des Baumes sollte dabei immer nach Laufzeit-Aspekten erfolgen: Welche Bestandteile des Systems gehören zusammen? Wie hängen diese voneinander ab? Gibt es Teile des Systems, deren Ausfall nicht kritisch ist? Idealerweise isolieren die Supervisors Teile des Systems voneinander. Fällt ein Teil des Systems aus, kann dieser geordnet neu gestartet werden, ohne die restlichen Teile in Mitleidenschaft zu ziehen. Eine der großen Herausforderungen und auch der Teil, der am schwierigsten zu lernen ist, ist das Denken in dieser ungewohnten Systemarchitektur. Diese zwingt dazu, genauere Überlegungen über Abhängigkeiten und Fehlerbilder der Systemteile anzustellen.
Let it crash
In Erlang ist es nicht ungewöhnlich, keine explizite Fehlerbehandlung zu programmieren. Hier gilt der Spruch "let it crash", denn im Normalfall wird ein derart abstürzender Prozess von seinem zugehörigen Supervisor direkt wieder hochgefahren. Nur der interne Zustand des jeweiligen Prozesses geht verloren, sofern er nicht vorher explizit gesichert wurde. Dies kann zum Beispiel den Umgang mit unzuverlässigen externen Systemen vereinfachen: Nach dem Stellen einer API-Anfrage wird eine bestimmte Zeit – zum Beispiel fünf Sekunden – gewartet und sollte in dieser Zeit keine Antwort oder nur eine Fehlermeldung eintreffen, so wird dies protokolliert und der Prozess crasht. Bei der nächsten Anfrage an die unzuverlässige API wird ein neuer Prozess gestartet und das Spiel beginnt von Neuem. Eine Ausnahmebehandlung muss dabei nicht programmiert werden.
Jeder Supervisor hat entweder andere Arbeits-Prozesse unter sich, oder weitere Supervisor-Prozesse. Beim von OTP standardmäßig angebotenen Supervisor müssen aber alle zu verwaltenden Prozesse bereits bekannt sein. Soll die Anzahl der Prozesse dynamisch sein, zum Beispiel beim bereits als Beispiel erwähnten Webserver, bei dem für jede Anfrage ein neuer Prozess gestartet wird, so wird dafür der ebenfalls in OTP enthaltene Dynamic Supervisor benötigt. Dieser funktioniert identisch zum einfachen Supervisor, bietet aber einige Zusatzfunktionen zur Erzeugung und Verwaltung einer dynamischen Menge von Unterprozessen.
In Abb. 4 ist schematisch ein Beispielsystem für ein Onlinespiel abgebildet: Hier werden die einzelnen Systemteile innerhalb des Supervisor-Baumes voneinander isoliert verwaltet. Fehler sind dabei jeweils innerhalb eines Supervisor-Teilbaumes isoliert und breiten sich nicht in die anderen Teilbäume aus. Dies führt zu einem fehlertoleranten System, da Dienste wie ein Chat-System oder eine Spieleraktions-Protokollierung nicht betriebskritisch sind. Fallen diese aus, bekommt der Anwender zwar eine eingeschränkte Funktionalität präsentiert, kann den Kern des Systems aber weiter benutzen. Selbst beim Ausfall des Logins könnten bereits eingeloggte Spieler weiterspielen. Die entsprechenden Laufzeit-Abhängigkeiten müssen beim Systemdesign ermittelt und berücksichtigt werden. Es kann aber auf eine explizite, vielleicht gar hierarchische, Fehlerbehandlung in jeder einzelnen Code-Komponente verzichtet werden.
Fazit
Der Einstieg in die Programmierung mit Elixir ist nicht schwierig: Die Syntax lässt sich gut erlernen und die funktionalen Aspekte der Sprache stehen nicht im Vordergrund. Viele Ansätze sind pragmatisch gewählt und stehen daher beim Programmieren nicht im Weg. Auch das gute Tooling und das lebendige Ökosystem sowie die äußerst hilfreiche Community helfen dabei, schnell erste Ergebnisse zu erzielen. Allerdings erfordert die durch Erlang bedingte Verwendung des Aktor-Modells doch eine gewisse Eingewöhnung. Das Denken in Supervisor-Bäumen und Prozessen, die nur über den Austausch von Nachrichten lose miteinander gekoppelt sind, muss erlernt und geübt werden. Praktischerweise sind viele Konzepte, die hier schon seit 30 Jahren Anwendung finden, mittlerweile über Microservices und Self-Contained-Systems weiter verbreitet und bekannter als noch vor einigen Jahren.
Die leichtgewichtigen Prozesse sorgen zusammen mit dem cleveren Scheduler der Erlang VM dafür, dass der eigene Code gut skaliert, ohne dass dafür besonders viel Aufwand betrieben werden müsste. Dabei bringen auch unerwartete Auslastungsspitzen keine übergroße Verschlechterung der Antwortzeiten mit sich. Ein Aspekt, auf den im Artikel nicht eingegangen wurde, der aber bei größeren Systemen Relevanz bekommt, ist, dass die einzelnen Prozesse auch auf unterschiedlichen Systemen laufen können: Erlang bringt alle notwendigen Techniken und Hilfsmittel direkt mit, um verteilte Systeme bauen zu können. Außerdem bietet Erlang standardmäßig hervorragende Monitoring-Tools: Es kann jederzeit detailliert in ein laufendes System geschaut werden. Dabei kann der aktuelle Zustand abgefragt und versendete Nachrichten können nachgelesen werden, ohne dazu erst einen Tracing-Modus oder eine explizite Protokollierung aktivieren zu müssen.
Dadurch, dass aufgrund des Aktor-Modells und des definierten Verhaltens der Erlang-Prozesse praktisch kaum Synchronisierungs-Code notwendig ist, wird die nebenläufige Programmierung sehr viel einfacher und zugleich robuster als in den gängigen Mainstream-Programmiersprachen wie Java und C#. Eine erhebliche Anzahl an Fehlerquellen, die sonst in der Multithreaded-Programmierung zu beachten sind, entfällt dadurch einfach.
Spätestens nach Umsetzung eines Systems mit Elixir und Erlang wirkt der Umgang mit Nebenläufigkeit in den meisten anderen Programmiersprachen sehr umständlich. Der Kontakt mit dem Aktor-Modell und der Organisation von Laufzeit-Abhängigkeiten via Supervisor-Bäumen erweitert den Horizont und hilft auch bei der Entwicklung in anderen Sprachen und Architekturen. Schon dafür lohnt sich eine Beschäftigung mit Erlang und Elixir in jedem Fall.
Neuen Kommentar schreiben