در Elixir، همهی کدها درون «فرایندها» اجرا میشوند.
بهطور پیشفرض، هر تابع در همان فرایندی اجرا میشود که از آنجا فراخوانی شده است. وقتی لازم است تابع خاصی را صراحتاً در یک فرایند جدید اجرا کنید، از spawn/1 استفاده کنید:
spawn(fn -> 2 + 2 end)
# => #PID<0.125.0>
spawn/1 فرایند جدیدی میسازد که تابع بدون آرگومانِ دادهشده را اجرا میکند و یک «شناسهی فرایند» را برمیگرداند که به اختصار PID نامیده میشود. فرایند جدید تا زمانی که تابع در حال اجراست زنده میماند و پس از آن بیصدا از بین میرود.
فرایندهای Elixir را نباید با فرایندهای سیستمعامل اشتباه گرفت. فرایندهای Elixir حافظه و CPU بسیار کمتری مصرف میکنند. هیچ اشکالی ندارد که یک برنامهی Elixir صدها فرایند Elixir اجرا کند.
فرایندها اطلاعات را مستقیماً با یکدیگر به اشتراک نمیگذارند. فرایندها برای به اشتراک گذاشتن داده، پیام میفرستند و دریافت میکنند.
میتوانید با send/2 به هر فرایندی پیام بفرستید. آرگومان اول send/2 شناسهی فرایند گیرنده است و آرگومان دوم پیام.
پیام میتواند از هر نوعی باشد. اغلب از اتمها و تاپلها تشکیل میشود. اگر میخواهید پاسخ بگیرید، باید شناسهی فرایند فرستنده را جایی در پیام بگنجانید. با self() میتوانید شناسهی فرایند جاری را به دست آورید.
send/2 بررسی نمیکند که پیام توسط گیرنده دریافت شده است یا نه، و نه اینکه گیرنده هنوز زنده است یا نه. پیام در «صندوق پیام» گیرنده مینشیند و تنها در صورتی خوانده میشود که گیرنده صراحتاً درخواست دریافت پیامها کند.
میتوان پیام را با ماکروی receive/1 از صندوق پیام خواند. این ماکرو یک بلوک do میپذیرد که میتواند روی پیامها تطبیق الگو انجام دهد.
receive do
{:ping, sender_pid} -> send(sender_pid, :pong)
:do_nothing -> nil
end
receive/1 یک پیام را از صندوق پیام برمیدارد که با هر یک از الگوهای دادهشده مطابقت داشته باشد و عبارت مربوط به آن الگو را اجرا میکند. اگر پیامی در صندوق پیام نباشد، یا هیچیک از پیامهای صندوق پیام با هیچیک از الگوها مطابقت نداشته باشد، receive/1 منتظر میماند تا پیامی برسد.
اگر میخواهید بیش از یک پیام دریافت کنید، باید receive/1 را بهصورت بازگشتی فراخوانی کنید. الگوی رایجی است که یک تابع بازگشتی پیادهسازی کنید، مثلاً با اسم loop، که receive/1 را فراخوانی میکند، کاری با پیام انجام میدهد و سپس خودش را فراخوانی میکند تا منتظر پیامهای بیشتر بماند. اگر لازم است وضعیتی را از یک فراخوانی receive/1 به فراخوانی دیگر منتقل کنید، میتوانید این کار را با پاس دادن آرگومانی به آن تابع loop انجام دهید.
شناسههای فرایند خودشان یک نوع دادهی جداگانهاند. اینها بهعنوان نشانی صندوق پیام عمل میکنند: اگر شناسهی یک فرایند را داشته باشید، میتوانید به آن فرایند پیام بفرستید. شناسههای فرایند معمولاً بهصورت غیرمستقیم ساخته میشوند، بهعنوان مقدار بازگشتی توابعی که فرایندهای جدید میسازند، مانند spawn.
شما در حال نوشتن یک سیستم نهفته برای یک دستگاه نوبتدهی هستید. مدل بسیار سادهای است. میتواند شمارههای متوالی بدهد و گزارش کند که آخرین شمارهی دادهشده چه بوده است.
تابع start/0 را پیادهسازی کنید. باید یک فرایند جدید ایجاد کند و PID آن فرایند را برگرداند. فرایند جدید فعلاً لازم نیست کاری انجام دهد.
TakeANumber.start()
# => #PID<0.138.0>
توجه کنید که هر بار این کد را اجرا میکنید، ممکن است PID متفاوت باشد.
دستگاه را طوری تغییر دهید که فرایند تازه ایجادشده، با وضعیت اولیهی 0، آمادهی دریافت پیامها باشد (یک حلقهی دریافت راه بیندازد). باید بتواند پیامهای {:report_state, sender_pid} را دریافت کند. در پاسخ به این پیامها، باید وضعیت فعلی خود (آخرین شمارهی بلیت دادهشده) را به sender_pid بفرستد و سپس منتظر پیامهای بیشتر بماند.
machine_pid = TakeANumber.start()
# a client sending a message to the machine
send(machine_pid, {:report_state, self()})
# a client receiving a message from the machine
receive do
msg -> msg
end
# => 0
دستگاه را طوری تغییر دهید که بتواند پیامهای {:take_a_number, sender_pid} را دریافت کند. باید وضعیتش را ۱ واحد افزایش دهد، وضعیت جدید را به sender_pid بفرستد و سپس منتظر پیامهای بیشتر بماند.
machine_pid = TakeANumber.start()
# a client sending a message to the machine
send(machine_pid, {:take_a_number, self()})
# a client receiving a message from the machine
receive do
msg -> msg
end
# => 1
دستگاه را طوری تغییر دهید که بتواند پیام :stop را دریافت کند. باید انتظار برای پیامهای بیشتر را متوقف کند.
دستگاه را طوری تغییر دهید که وقتی پیامی غیرمنتظره دریافت میکند، آن را نادیده بگیرد و همچنان منتظر پیامهای بیشتر بماند.