Parallele Anwendungen entwickeln mit Erlang/OTP

Seite 4: Variablen

Inhaltsverzeichnis

Erlang ist sequenziell funktional ausgelegt. Jede Funktion liefert das Ergebnis des letzten Ausdrucks zurück. Wie viele funktionale Sprachen verfügt Erlang über Higher Order Functions, die sich bei der Entwicklung von Modulen wie lists als hilfreich erweisen. Das lists-Modul bietet Funktionen wie filter, fold, foreach und map zur Anwendung anonymer Funktionen auf Listen. Das Ergebnis sind neue Listen oder im Falle von fold das berechnete Ergebnis. Listen selbst lassen sich nicht verändern. Gleiches gilt für Tupel, Records oder beliebige andere Variablen. Hier stutzen Umsteiger in der Regel: Erlang-Variablen lassen sich nach einer initialen Zuweisung nicht mehr modifizieren. Die Motivation hierfür liegt unter anderem in der Vermeidung von Seiteneffekten. So führt in vielen Sprachen die Modifikation von Variablen, die als Referenz an eine Funktion übergeben werden, häufig zu schwer nachvollziehbaren Fehlern. Dies vermeidet Erlang. Gleichzeitig erleichtern Variablen mit einmaliger Zuweisung die Realisierung der wichtigsten Spracheigenschaft neben dem funktionalen Charakter: der Nebenläufigkeit.

Prozesse sind in Erlang leichtgewichtig und unabhängig vom Betriebssystem ausgelegt. Ihre Ausführung erfolgt durch die virtuelle Maschine, sodass sie sich auf unterschiedlichen Plattformen einheitlich verhalten. Gleichzeitig sorgt die VM für die Aufteilung der Prozesse auf die konfigurierten Kerne oder Prozessoren. Der Datenaustausch zwischen den Prozessen erfolgt im Gegensatz zu vielen Thread-Implementierungen nicht im Shared Memory. Vielmehr verfügt jeder Prozess über eine eigene Message Queue, an die weitere Prozesse – auch über Node-Grenzen hinweg – Nachrichten senden können. Mittels receive und dem bereits genannten Pattern Matching kann der Empfänger die Nachrichten selektiv auslesen und verarbeiten. Über endrekursive Aufrufe lassen sich so interne Dienste implementieren, die mit einem kleinen Trick auch über einen Status verfügen können.

-module(module_f).
-export([start/1]).
start(InitialState) ->
register(my_server, spawn(module_f, loop, [InitialState])).
% Process loop.
loop(State) ->
receive
{do_this, Arg1, Arg2} ->
NewState = handle_this(Arg1, Arg2, State),
loop(NewState);
{do_that, Arg1} ->
NewState = handle_that(Arg1, State),
loop(NewState);
stop ->
ok
end.
% handle_this and handle_that have to return the new state.
...

Dieses Verfahren findet sich in einer Vielzahl der mitgelieferten Module wieder.

Die so erzeugten Prozesse kann man entweder anonym starten – in diesem Fall müssen aufrufende Prozesse die Prozess-ID kennen – oder sie mit einem einmaligen Namen registrieren. Dieser lässt sich dann für das Versenden der Nachricht nutzen. Als Operator hierfür fungiert das Ausrufezeichen (!).

-module(module_g).
-export([server_test/0]).
server_test() ->
module_f:start(0),
my_server ! {do_this, 4711, foo},
my_server ! {do_that, bar},
my_server ! stop.

Allerdings wird es in der Regel in exportierten Komfortfunktionen versteckt, sodass der Entwickler nicht immer auf den ersten Blick entdeckt, dass seine Anwendung asynchron mit einem Prozess kommuniziert. Auch beim Laufzeitverhalten fällt das nicht auf. So können Erlang-Nodes problemlos mehrere 10 000 Prozesse parallel betreiben und dabei je nach Systemlast dennoch Antwortzeiten kleiner als eine Millisekunde liefern. Die effizient arbeitende Garbage Collection trägt hierzu bei.

Wie erwähnt lassen sich mehrere Nodes vernetzen. Eine einfache Authentifizierung soll das unbefugte Integrieren von Nodes verhindern. Jeder Knoten verfügt über einen automatisch oder manuell vergebenen Namen. Über diesen kann man Prozesse auf entfernten Nodes starten oder vorhandene ansprechen. Bis auf diesen Namen unterscheiden sich der lokale und der entfernte Nachrichtenversand nicht. Das Marshalling der Daten erfolgt für den Nutzer vollkommen transparent. Zusätzlich zu den Spracheigenschaften unterstützen mitgelieferte Module die Arbeit mit Prozessen auf unterschiedlichen Knoten. Auf diese Weise sind verteilte Systeme leicht zu implementieren.

Diverse weitere Eigenschaften erleichtern die Entwicklung von Anwendungssystemen. Supervisor Trees können Prozesse automatisch informieren, wenn ein Kindprozess stirbt, Gleiches lässt sich manuell über Links oder Monitors erreichen, ebenfalls wieder über Node-Grenzen hinweg oder für ganze Nodes. So kann eine Anwendung Fehler korrigieren oder automatisch die Funktionen eines fehlerhaften Knotens übernehmen. Konstrukte für die Ausnahmebehandlung sowie die Fähigkeit, Software-Upgrades zur Laufzeit durchzuführen, erhöhen die Stabilität und Systemlaufzeit zusätzlich.