فرایندهای Elixir ایزوله هستند و بهطور پیشفرض هیچ اشتراکی با یکدیگر ندارند. وقتی یک فرایند فرزندِ پیوندنخورده کرش میکند، فرایند والدِ آن تحت تأثیر قرار نمیگیرد.
این رفتار را میتوان با پیوند دادن فرایندها به یکدیگر تغییر داد. اگر دو فرایند به هم پیوند خورده باشند، خرابی در یکی از آنها به فرایند دیگر منتقل میشود. پیوندها دوطرفه هستند.
میتوان فرایندها را با spawn_link/1 از همان ابتدا پیوندخورده به فرایند فراخوانیکننده ایجاد کرد، که یک عملیات اتمی است، یا میتوان بعداً آنها را با Process.link/1 پیوند زد.
پیوند دادن فرایندها هنگام انجام کارهای موازی میتواند مفید باشد؛ در جایی که اگر یک تکهکار نتواند به پایان برسد، نباید تکهکارهای دیگر ادامه یابند.
از پیوند دادن میتوان برای نظارت بر فرایندها هم استفاده کرد. اگر فرایندی خروجها را به تله بیندازد، وقتی فرایندی که به آن پیوند خورده کرش کند، خودش کرش نمیکند. در عوض، پیامی دربارهی کرش دریافت میکند. این کار به آن امکان میدهد که بهنرمی با کرش کنار بیاید، مثلاً با راهاندازی مجدد فرایند کرششده.
میتوان فرایندی را طوری تنظیم کرد که با فراخوانی Process.flag(:trap_exit, true) خروجها را به تله بیندازد. توجه کنید که Process.flag/2 مقدار قبلی پرچم را برمیگرداند، نه مقدار جدید.
پیامی که در صورت کرش یک فرایند پیوندخورده به فرایند فرستاده میشود با الگوی {:EXIT, from, reason} مطابقت دارد، که در آن from یک PID است. اگر reason مقداری جز اتم :normal باشد، یعنی فرایند کرش کرده یا بهزور کشته شده است.
تسکها فرایندهایی هستند که برای اجرای یک عملیات مشخص در نظر گرفته شدهاند. آنها معمولاً با فرایندهای دیگر ارتباط برقرار نمیکنند، اما میتوانند نتیجهای را به فرایندی که تسک را شروع کرده برگردانند.
از تسکها معمولاً برای موازیسازی کارها استفاده میشود.
async/await
برای شروع یک تسک، از Task.async/1 استفاده کنید. این تابع یک تابع بینام را بهعنوان آرگومان میگیرد و آن را در یک فرایند جدید که به فرایند فراخوانیکننده پیوند خورده اجرا میکند. یک ساختار %Task{} برمیگرداند.
برای گرفتن نتیجهی اجرا، ساختار %Task{} را به Task.await/2 بدهید. این تابع صبر میکند تا تسک تمام شود و نتیجهاش را برمیگرداند. آرگومان دوم یک مهلت زمانی بر حسب میلیثانیه است که مقدار پیشفرضش ۵۰۰۰ است.
توجه کنید که بین شروع تسک و انتظار برای آن، فرایندی که تسک را شروع کرده مسدود نمیشود و ممکن است کارهای دیگری انجام دهد.
هر تسکی که با Task.async/1 شروع میشود باید await شود، چون پیامی به فرایند فراخوانیکننده میفرستد. Task.await/2 برای هر تسک فقط یکبار میتواند فراخوانی شود.
start/start_link
اگر میخواهید تسکی را فقط برای عوارض جانبی شروع کنید، از Task.start/1 یا Task.start_link/1 استفاده کنید. Task.start/1 تسکی را شروع میکند که به فرایند فراخوانیکننده پیوند نخورده است و Task.start_link/1 تسکی را شروع میکند که به فرایند فراخوانیکننده پیوند خورده است. هر دو تابع یک تاپل {:ok, pid} برمیگردانند.
کار شما در Instruments of Texas روی یک ماشینحساب RPN آزمایشی ادامه دارد. تیم شما چند نمونهی اولیه ساخته است که باید بررسی دقیقی روی آنها انجام شود تا بهترین نمونهای انتخاب شود که میتوان آن را به تولید انبوه رساند.
شما میخواهید دو نوع بررسی انجام دهید.
نخست، بررسی اطمینانپذیری که ورودیهایی را تشخیص میدهد که ماشینحسابِ تحت بررسی برای آنها یا از کار میافتد یا بهاندازهی کافی سریع پاسخ نمیدهد. برای جدا کردن خطاها، محاسباتِ هر ورودی باید در یک فرایند جداگانه اجرا شود. میتوان از لینک کردن و تلهگذاری خروجها در فرایند فراخوان استفاده کرد تا تشخیص داد که محاسبه تمام شده است یا از کار افتاده است.
دوم، بررسی درستی که بررسی میکند آیا برای یک ورودی مشخص، نتیجهای که ماشینحساب برمیگرداند همان چیزی است که انتظار میرود یا نه. تنها ماشینحسابهایی که پیشتر بررسی اطمینانپذیری را گذراندهاند بررسی درستی روی آنها انجام میشود، بنابراین از کار افتادن نگرانیای ایجاد نمیکند. با این حال، برای سریعتر شدن فرایند، عملیات باید بهصورت همزمان اجرا شوند و همین آن را به مورد استفادهی ایدهآل برای کارهای ناهمگام تبدیل میکند.
تابع RPNCalculatorInspection.start_reliability_check/2 را پیادهسازی کنید. این تابع باید ۲ آرگومان بگیرد: یک تابع (ماشینحساب) و یک ورودی برای ماشینحساب. باید یک «نگاشت» برگرداند که ورودی و شناسهی فرایندِ ایجادشده را در خود دارد.
فرایند ایجادشده باید تابع ماشینحسابِ دادهشده را با ورودی دادهشده فراخوانی کند. این فرایند باید به فرایند فراخوان لینک شود.
RPNCalculatorInspection.start_reliability_check(fn _ -> 0 end, "2 3 +")
# => %{input: "2 3 +", pid: #PID<0.169.0>}
تابع RPNCalculatorInspection.await_reliability_check_result/2 را پیادهسازی کنید. این تابع باید دو آرگومان بگیرد. آرگومان اول نگاشتی است که ورودیِ بررسی اطمینانپذیری و شناسهی فرایندی را در خود دارد که بررسی اطمینانپذیری را برای این ورودی اجرا میکند، همانطور که RPNCalculatorInspection.start_reliability_check/2 برمیگرداند. آرگومان دوم نگاشتی است که بهعنوان «انباشتگر» نتایج بررسیهای اطمینانپذیری با ورودیهای مختلف عمل میکند.
تابع باید منتظر یک پیام خروج بماند.
اگر پیام خروجی ({:EXIT, from, reason}) با دلیل :normal را از همان فرایندی دریافت کند که بررسی اطمینانپذیری را اجرا میکند، باید نگاشت نتایج را با مقدار :ok که زیر کلید input افزوده شده است برگرداند.
اگر پیام خروجی با دلیلی متفاوت را از همان فرایندی دریافت کند که بررسی اطمینانپذیری را اجرا میکند، باید نگاشت نتایج را با مقدار :error که زیر کلید input افزوده شده است برگرداند.
اگر در ۱۰۰ میلیثانیه هیچ پیامی که با این معیارها مطابقت داشته باشد دریافت نکند، باید نگاشت نتایج را با مقدار :timeout که زیر کلید input افزوده شده است برگرداند.
# when an exit message is waiting for the process in its inbox
send(self(), {:EXIT, pid, :normal})
RPNCalculatorInspection.await_reliability_check_result(
%{input: "5 7 -", pid: pid},
%{}
)
# => %{"5 7 -" => :ok}
# when there are no messages in the process inbox
RPNCalculatorInspection.await_reliability_check_result(
%{input: "3 2 *", pid: pid},
%{"5 7 -" => :ok}
)
# => %{"5 7 -" => :ok, "3 2 *" => :timeout}
تابع RPNCalculatorInspection.reliability_check/2 را پیادهسازی کنید. این تابع باید ۲ آرگومان بگیرد: یک تابع (ماشینحساب) و فهرستی از ورودیها برای ماشینحساب.
برای هر ورودی در فهرست، باید با استفاده از start_reliability_check/2 بررسی اطمینانپذیری را در یک فرایند لینکشدهی جدید آغاز کند. سپس، برای هر فرایندی که به این شکل آغاز شده است، باید با استفاده از await_reliability_check_result/2 منتظر نتایج آن بماند.
پیش از آغاز هر فرایندی، تابع باید روی فرایند جاری پرچمِ تلهگذاری خروجها را فعال کند تا بتواند پیامهای خروج را دریافت کند. پس از آن، باید این پرچم را به مقدار اولیهاش بازگرداند.
تابع باید نگاشتی را برگرداند که نتایج بررسیهای اطمینانپذیری همهی ورودیها را در خود دارد.
fake_broken_calculator = fn input ->
if String.ends_with?(input, "*"), do: raise("oops")
end
inputs = ["2 3 +", "10 3 *", "20 2 /"]
RPNCalculatorInspection.reliability_check(fake_broken_calculator, inputs)
# => %{
# "2 3 +" => :ok,
# "10 3 *" => :error,
# "20 2 /" => :ok
# }
تابع RPNCalculatorInspection.correctness_check/2 را پیادهسازی کنید. این تابع باید ۲ آرگومان بگیرد: یک تابع (ماشینحساب) و فهرستی از ورودیها برای ماشینحساب.
برای هر ورودی در فهرست، باید یک کار ناهمگام آغاز کند که ماشینحساب را با ورودی دادهشده فراخوانی میکند. سپس، برای هر کاری که به این شکل آغاز شده است، باید ۱۰۰ میلیثانیه منتظر نتایج آن بماند.
fast_cheating_calculator = fn input -> 14 end
inputs = ["13 1 +", "50 2 *", "1000 2 /"]
RPNCalculatorInspection.correctness_check(fast_cheating_calculator, inputs)
# => [14, 14, 14]