In Elixir läuft der gesamte Code in Prozessen ab.
Standardmäßig wird eine Funktion in demselben Prozess ausgeführt, aus dem sie aufgerufen wurde. Wenn du eine bestimmte Funktion ausdrücklich in einem neuen Prozess ausführen möchtest, verwende spawn/1:
spawn(fn -> 2 + 2 end)
# => #PID<0.125.0>
spawn/1 erzeugt einen neuen Prozess, der die übergebene Funktion ohne Argumente ausführt, und gibt eine Prozesskennung (PID) zurück. Der neue Prozess bleibt am Leben, solange die Funktion läuft, und beendet sich danach still.
Prozesse in Elixir sollte man nicht mit Betriebssystemprozessen verwechseln. Prozesse in Elixir verbrauchen deutlich weniger Speicher und CPU. Es ist völlig in Ordnung, wenn eine Elixir-Anwendung Hunderte von Elixir-Prozessen ausführt.
Prozesse teilen Informationen nicht direkt miteinander. Prozesse senden und empfangen Nachrichten, um Daten auszutauschen.
Mit send/2 kannst du einem beliebigen Prozess eine Nachricht senden. Das erste Argument von send/2 ist die PID des Empfängers, das zweite Argument ist die Nachricht.
Eine Nachricht kann von beliebigem Typ sein. Oft besteht sie aus Atomen und Tupeln. Wenn du eine Antwort erhalten möchtest, solltest du die PID des Absenders irgendwo in der Nachricht unterbringen. Die PID des aktuellen Prozesses erhältst du mit self().
send/2 prüft nicht, ob die Nachricht beim Empfänger angekommen ist, und auch nicht, ob der Empfänger noch läuft. Die Nachricht landet im Postfach des Empfängers und wird nur gelesen, wenn der Empfänger ausdrücklich darum bittet, Nachrichten zu empfangen.
Mit dem Makro receive/1 kannst du eine Nachricht aus einem Postfach lesen. Es akzeptiert einen do-Block, der einen Musterabgleich mit den Nachrichten durchführen kann.
receive do
{:ping, sender_pid} -> send(sender_pid, :pong)
:do_nothing -> nil
end
receive/1 nimmt eine Nachricht aus dem Postfach, die auf eines der angegebenen Muster passt, und führt den zu diesem Muster angegebenen Ausdruck aus. Wenn sich keine Nachrichten im Postfach befinden oder keine der Nachrichten auf eines der Muster passt, wartet receive/1 auf eine.
Wenn du mehr als eine Nachricht empfangen möchtest, musst du receive/1 rekursiv aufrufen. Ein häufiges Muster ist eine rekursive Funktion, zum Beispiel mit dem Namen loop, die receive/1 aufruft, etwas mit der Nachricht macht und sich dann selbst aufruft, um auf weitere Nachrichten zu warten. Wenn du einen Zustand von einem receive/1-Aufruf zum nächsten mitnehmen musst, kannst du das tun, indem du der loop-Funktion ein Argument übergibst.
Prozesskennungen sind ein eigener Datentyp. Sie fungieren als Postfachadressen: Wenn du die PID eines Prozesses hast, kannst du diesem Prozess eine Nachricht senden. PIDs entstehen normalerweise indirekt als Rückgabewert von Funktionen, die neue Prozesse erzeugen, wie spawn.
Du schreibst ein eingebettetes System für einen Wartenummern-Automaten. Es ist ein sehr einfaches Modell. Er kann fortlaufende Nummern ausgeben und melden, welche Nummer zuletzt ausgegeben wurde.
Implementiere die Funktion start/0. Sie soll einen neuen Prozess erzeugen und die PID des Prozesses zurückgeben. Der neue Prozess muss noch nichts tun.
TakeANumber.start()
# => #PID<0.138.0>
Beachte, dass die PID bei jedem Ausführen dieses Codes anders sein kann.
Passe den Automaten so an, dass der neu erzeugte Prozess bereit ist, Nachrichten zu empfangen (starte eine Empfangsschleife), mit einem Anfangszustand von 0. Er soll Nachrichten {:report_state, sender_pid} empfangen können. Als Antwort auf solche Nachrichten soll er seinen aktuellen Zustand (die zuletzt ausgegebene Nummer) an sender_pid senden und dann auf weitere Nachrichten warten.
machine_pid = TakeANumber.start()
# a client sending a message to the machine
send(machine_pid, {:report_state, self()})
# a client receiving a message from the machine
receive do
msg -> msg
end
# => 0
Passe den Automaten so an, dass er Nachrichten {:take_a_number, sender_pid} empfangen kann. Er soll seinen Zustand um 1 erhöhen, den neuen Zustand an sender_pid senden und dann auf weitere Nachrichten warten.
machine_pid = TakeANumber.start()
# a client sending a message to the machine
send(machine_pid, {:take_a_number, self()})
# a client receiving a message from the machine
receive do
msg -> msg
end
# => 1
Passe den Automaten so an, dass er eine :stop-Nachricht empfangen kann. Er soll aufhören, auf weitere Nachrichten zu warten.
Passe den Automaten so an, dass er eine unerwartete Nachricht ignoriert und weiter auf weitere Nachrichten wartet.
Melde dich bei Exercism an, um Elixir mit 58 Konzepte168 Übungen und echtem menschlichen Mentoring zu lernen und zu meistern, alles kostenlos.