لی

لینک در Elixir

1 تمرین

درباره‌ی لینک

فرایندهای 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.

ویرایش از طریق GitHub این پیوند در پنجره یا زبانه‌ی جدیدی باز می‌شود

لینک را یاد بگیرید