Pr

Prozesse in Elixir

5 Übungen

Über Prozesse

In Elixir läuft der gesamte Code innerhalb von Prozessen. Elixir-Prozesse:

  • sind nicht mit Systemprozessen zu verwechseln.
  • sind leichtgewichtig.
  • haben bestimmte Anwendungsfälle. Sie können:
  • Es ist normal, dass eine Elixir-App hunderte Prozesse ausführt, aber auch eine, die gar keine neuen Prozesse ausdrücklich erzeugt, besonders wenn es eine Bibliothek ist.

Prozesse erzeugen

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:

  • spawn/1 akzeptiert eine Funktion, die es direkt ausführt.

    spawn(fn -> 2 + 2 end)
    # => #PID<0.125.0>
    
  • spawn/3 akzeptiert eine Funktion, die es anhand des Modulnamens, des Funktionsnamens (als Atom) und einer Liste von Argumenten ausführt, die an diese Funktion übergeben werden.

    spawn(String, :split, ["hello there", " "])
    # => #PID<0.113.0>
    
    • Dieses Datentripel wird oft MFA genannt: Module, Function, Arguments.
  • Ein Prozess wird beendet, sobald seine Funktion fertig ausgeführt wurde.

  • Du kannst mit Process.alive?/1 prüfen, ob ein Prozess noch lebt (also ausgeführt wird):

    pid = spawn(fn -> 2 + 2 end)
    Process.alive?(pid)
    # => false
    

Nachrichten

Prozesse teilen Informationen nicht direkt miteinander. Prozesse senden Nachrichten, um Daten auszutauschen. Dieses Nebenläufigkeitsmuster heißt Aktormodell.

  • Sende mit send/2 Nachrichten an einen Prozess.

    send(pid, :hello)
    
    • Die Nachricht landet in der Reihenfolge des Versendens in der Mailbox des Empfängers.
    • send prüft weder, ob die Nachricht empfangen wurde, noch, ob der Empfänger noch lebt.
  • Eine Nachricht kann von beliebigem Typ sein.

  • Mit receive/1 kannst du eine Nachricht empfangen, die an den aktuellen Prozess gesendet wurde.

    • Du musst die Nachrichten per Pattern Matching prüfen.
    • receive wartet, bis eine Nachricht, die auf eines der angegebenen Muster passt, in der Mailbox des Prozesses liegt.
      • Standardmäßig wartet es unbegrenzt, aber du kannst ihm mit einem after-Block ein Timeout geben.
    • Gelesene Nachrichten werden aus der Mailbox des Prozesses entfernt. Ungelesene Nachrichten bleiben dort unbegrenzt liegen.
      • Schreibe in receive/1 immer eine _-Klausel, die alles abfängt, damit dir durch aufgestaute ungelesene Nachrichten nicht der Speicher ausgeht.
    receive do
      {:ping, sender_pid} -> send(sender_pid, :pong)
      _ -> nil
    after
      5000 ->
        {:error, "No message in 5 seconds"}
    end
    

Empfangsschleife

Wenn du mehr als eine Nachricht empfangen möchtest, musst du receive/1 rekursiv aufrufen. Ein gängiges 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 ihn als Argument an diese Funktion loop übergeben.

def loop(state) do
  receive do
    :increment_by_one ->
      loop(state + 1)

    {:report_state, sender_pid} ->
      send(sender_pid, state)
      loop(state)

    :stop ->
      nil

    _ ->
      loop(state)
  end
end

In der Praxis wird dieser Ansatz nur selten direkt verwendet. Elixir bietet Abstraktionen für Nebenläufigkeit, etwa das Agent-Modul oder ein GenServer-Behaviour, die beide auf der Empfangsschleife aufbauen. Um diese Abstraktionen effizient nutzen zu können, ist es aber entscheidend, diese Grundlagen zu verstehen.

Über GitHub bearbeiten Der Link öffnet sich in einem neuen Fenster oder Tab

Lerne Prozesse

Das Üben ist gesperrt

Schalte 4 weitere Übungen frei, um Prozesse zu üben