فرایندهای 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 شناسهی یک فرایند است. اگر 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 فراهم میکند.
سایر انتزاعهای مربوط به فرایندها، مانند Agent، GenServer یا Task، دو راه برای راهاندازی آنها در اختیار میگذارند: یا start یا start_link.