Οι διεργασίες της Elixir είναι απομονωμένες και από προεπιλογή δεν μοιράζονται τίποτα μεταξύ τους. Όταν μια θυγατρική διεργασία που δεν είναι συνδεδεμένη καταρρέει, η γονική της διεργασία δεν επηρεάζεται.
parent_pid =
spawn(fn ->
spawn(fn -> raise "oops" end)
receive do
message -> IO.inspect(message, label: "received message")
end
end)
# => 20:03:08.405 [error] Process #PID<0.153.0> raised an exception
# ** (RuntimeError) oops
# (stdlib 3.13.2) erl_eval.erl:678: :erl_eval.do_apply/6
Process.alive?(parent_pid)
# => true
Αυτή η συμπεριφορά μπορεί να αλλάξει με τη σύνδεση των διεργασιών μεταξύ τους. Αν δύο διεργασίες είναι συνδεδεμένες, μια αποτυχία στη μία διεργασία θα μεταδοθεί στην άλλη. Οι σύνδεσμοι είναι αμφίδρομοι.
parent_pid =
spawn(fn ->
spawn_link(fn -> raise "oops" end)
receive do
message ->
IO.inspect(message, label: "received message")
end
end)
# => 20:05:34.125 [error] Process #PID<0.171.0> raised an exception
# ** (RuntimeError) oops
# (stdlib 3.13.2) erl_eval.erl:678: :erl_eval.do_apply/6
Process.alive?(parent_pid)
# => false
Οι διεργασίες μπορούν να εκκινηθούν ήδη συνδεδεμένες με τη διεργασία που τις καλεί χρησιμοποιώντας το spawn_link/1, το οποίο είναι μια ατομική πράξη, ή μπορούν να συνδεθούν αργότερα με το Process.link/1.
Μπορείς να ελέγξεις ποιες διεργασίες είναι αυτή τη στιγμή συνδεδεμένες με μια διεργασία (και εκτελούνται ακόμα) με το Process.info/1.
pid = spawn_link(fn -> :timer.sleep(50_000) end)
# => #PID<0.126.0>
self()
|> Process.info()
|> Keyword.get(:links)
# => [#PID<0.126.0>]
Η σύνδεση διεργασιών μπορεί να φανεί χρήσιμη όταν κάνεις παράλληλη εργασία, όταν δηλαδή κάθε κομμάτι εργασίας δεν θα πρέπει να συνεχιστεί σε περίπτωση που κάποιο άλλο κομμάτι αποτύχει να ολοκληρωθεί.
Η σύνδεση μπορεί επίσης να χρησιμοποιηθεί για την επίβλεψη διεργασιών. Αν μια διεργασία παγιδεύει τις εξόδους, δεν θα καταρρεύσει όταν καταρρεύσει μια διεργασία με την οποία είναι συνδεδεμένη. Αντ' αυτού θα λάβει ένα μήνυμα για την κατάρρευση. Αυτό της επιτρέπει να χειριστεί την κατάρρευση ομαλά, για παράδειγμα επανεκκινώντας τη διεργασία που κατέρρευσε.
Μια διεργασία μπορεί να ρυθμιστεί ώστε να παγιδεύει τις εξόδους καλώντας το Process.flag(:trap_exit, true). Σημείωσε ότι το Process.flag/2 επιστρέφει την παλιά τιμή της σημαίας, όχι τη νέα.
Την τρέχουσα τιμή της σημαίας μπορείς να την ελέγξεις με το Process.info/1.
pid
|> Process.info()
|> Keyword.get(:trap_exit)
# => false
Το μήνυμα που θα σταλεί στη διεργασία σε περίπτωση που καταρρεύσει μια συνδεδεμένη διεργασία θα ταιριάζει με το μοτίβο {:EXIT, from, reason}, όπου το from είναι ένα PID. Αν το reason είναι κάτι άλλο εκτός από το άτομο :normal, αυτό σημαίνει ότι η διεργασία κατέρρευσε ή σκοτώθηκε βίαια.
parent_pid =
spawn(fn ->
Process.flag(:trap_exit, true)
spawn_link(fn -> raise "oops" end)
receive do
message ->
IO.inspect(message, label: "received message")
# do something else,
# to demonstrate that the process stays alive
:timer.sleep(50_000)
end
end)
# => 20:05:34.125 [error] Process #PID<0.295.0> raised an exception
# ** (RuntimeError) oops
# (stdlib 3.13.2) erl_eval.erl:678: :erl_eval.do_apply/6
# received message: {:EXIT, #PID<0.295.0>,
# {%RuntimeError{message: "oops"},
# [{:erl_eval, :do_apply, 6, [file: 'erl_eval.erl', line: 678]}]}}
Process.alive?(parent_pid)
# => true
Σημείωσε ότι η παγίδευση των εξόδων κάνει τη διεργασία ανθεκτική σε καταρρεύσεις όλων των συνδεδεμένων διεργασιών της, και τα μηνύματα εξόδου μπορεί να συσσωρευτούν στο γραμματοκιβώτιο της διεργασίας αν μείνουν αδιάβαστα. Γι' αυτόν τον λόγο, θα πρέπει να παρακολουθείς όλες τις διεργασίες που είναι συνδεδεμένες με μια διεργασία που παγιδεύει τις εξόδους. Συνήθως, οι διεργασίες που παγιδεύουν τις εξόδους είναι υπεύθυνες μόνο για την επίβλεψη άλλων διεργασιών και δεν κάνουν τίποτα άλλο.
Στην πράξη, δεν θα υλοποιήσεις μόνος σου μια διεργασία επιτηρητή από την αρχή. Η Elixir παρέχει μια αφαίρεση για αυτού του είδους τη λειτουργικότητα, με τη μορφή του Supervisor behaviour.
Άλλες αφαιρέσεις γύρω από τις διεργασίες, όπως οι Agent, GenServer ή Task, παρέχουν δύο τρόπους εκκίνησής τους: είτε start είτε start_link.