ال

الروابط في 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 هو معرّف العملية (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.

كما توفّر تجريدات أخرى حول العمليات، مثل Agent وGenServer وTask، خيارَين لبدء تشغيلها: إما start أو start_link.

تعديل عبر GitHub يفتح الرابط في نافذة أو علامة تبويب جديدة

تعلّم الروابط