Node.js به دلیل مدل I/O غیربلاککننده و غیرهمزمان خود شناخته میشود که آن را به انتخابی بسیار کارآمد برای ساخت برنامههای مقیاسپذیر تبدیل کرده است. اما زمانی که با عملیات پردازشهای سنگین یا انجام چندین کار به صورت همزمان روبهرو میشویم، محدودیتهایی در Node.js وجود دارد. به طور پیشفرض، Node.js روی یک نخ (Thread) اجرا میشود، اما روشهایی برای کنار آمدن با این محدودیت وجود دارد. دو روش اصلی برای دستیابی به چند نخی با جاوااسکریپت در Fork ، Node js و worker threads هستند. در ادامه به آموزش Fork در Node js و برنامه نویسی چند نخی با جاوااسکریپت می پردازیم. همچنین می توانید از مقاله ی آموزش استفاده از Web worker در جاوا اسکریپت و پاراللیسم در وب نیز دیدن کنید.
Fork در Node js چیست؟
Fork در Node js فرایندی است که در آن یک فرزند (child) جدید از فرآیند اصلی ایجاد میشود. این فرآیند فرزند بهطور مستقل اجرا میشود، اما از همان کد استفاده میکند، و این امکان را میدهد که اجرا به صورت موازی انجام شود. این ویژگی برای انجام محاسبات سنگین بدون مسدود کردن نخ اصلی بسیار مفید است، زیرا این اطمینان حاصل میشود که نخ اصلی همچنان پاسخگو باقی میماند.
Fork با استفاده از توانایی سیستمعامل برای ایجاد فرآیندها کار میکند. هر فرآیند Fork شده در Node.js یک نمونه مجزا از حلقه رویداد، موتور V8 جاوااسکریپت و سایر منابع را اجرا میکند. این ویژگی Forking را به یک راهحل مناسب برای انجام کارهای موازی تبدیل میکند، اما با چالشها و ملاحظات عملکردی خاص خود نیز همراه است.
نحوه استفاده از Fork در Node js
در Node js، شما میتوانید یک فرآیند جدید را با استفاده از ماژول child_process
و متد fork()
ایجاد کنید. این ماژول امکاناتی را برای فراخوانی یک فرآیند فرزند در اختیار میگذارد.
در اینجا یک مثال ساده از نحوه استفاده از Forking آورده شده است:
const { fork } = require('child_process');
// ایجاد یک فرآیند فرزند
const child = fork('./child.js');
// ارسال پیامی به فرآیند فرزند
child.send({ message: 'سلام از طرف والد' });
// دریافت پیامی از فرآیند فرزند
child.on('message', (message) => {
console.log('دریافت شده از فرزند:', message);
});
در این مثال، فرآیند والد یک فرآیند جدید ایجاد میکند که فایل child.js
را اجرا میکند. والد میتواند با استفاده از متدهای send
و on('message')
با فرآیند فرزند ارتباط برقرار کند.
فرآیند فرزند (child.js):
process.on('message', (message) => {
console.log('دریافت شده از والد:', message);
// ارسال پیامی به والد
process.send({ message: 'سلام از طرف فرزند' });
});
چرا از Forking در Node js استفاده کنیم؟
Forking یک راهحل عالی است زمانی که شما نیاز به انجام کارهای زیر دارید:
- موازیسازی وظایف پردازش سنگین: در Node js، حلقه رویداد بهطور پیشفرض یکنخ است، به این معنا که کارهای سنگین CPU میتوانند حلقه رویداد را مسدود کرده و عملکرد کل برنامه را کاهش دهند. Forking این امکان را میدهد که چنین وظایفی را به فرآیندهای جداگانه منتقل کنید، بنابراین نخ اصلی همچنان پاسخگو میماند.
- جداسازی اجرا: از آنجا که هر فرآیند فرزند کاملاً ایزوله است، میتواند یک نمونه مجزا از موتور V8 را اجرا کند و بهطور مستقل از فرآیند والد عمل کند. این ایزوله بودن، مدیریت خطاها را سادهتر میکند و از تأثیر آنها بر فرآیند والد جلوگیری میکند.
- استفاده از پردازندههای چند هستهای: Node js بهطور پیشفرض روی یک نخ اجرا میشود، اما با استفاده از Forking، شما میتوانید چندین فرآیند فرزند ایجاد کنید که میتوانند روی هستههای مختلف پردازنده اجرا شوند. این ویژگی عملکرد برنامه شما را روی سیستمهای چند هستهای بهینه میکند.
نحوه عملکرد Forking در Node.js
هنگامی که شما یک فرآیند Node.js را Fork میکنید، در واقع یک نمونه جدید از Node.js به همراه یک نمونه مجزا از موتور V8 برای فرآیند فرزند ایجاد میشود. این بدان معناست که هر فرآیند دارای فضای حافظه و حلقه رویداد مستقل است، اما از همان کد استفاده میکند و میتواند از طریق ارتباطات بینفرآیندی (IPC) با یکدیگر ارتباط برقرار کنند.
در اینجا یک بررسی گام به گام از آنچه که در هنگام Forking رخ میدهد آورده شده است:
- فرآیند والد: فرآیند والد شروع به کار کرده و حلقه رویداد خود را اجرا میکند.
- Forking: وقتی که متد
fork()
فراخوانی میشود، یک فرآیند فرزند جدید ایجاد میشود. سیستمعامل یک شناسه فرآیند (PID) جدید به آن تخصیص میدهد و منابع جدیدی برای فرآیند فرزند اختصاص میدهد. - نمونه موتور V8: هر فرآیند Fork شده یک نمونه مجزا از موتور V8 را اجرا میکند، به این معنا که هر فرآیند دارای جمعآوری زباله (garbage collection)، زمینه اجرا (execution context) و حلقه رویداد خود است.
- ارتباطات IPC: فرآیندهای والد و فرزند میتوانند از طریق IPC با یکدیگر ارتباط برقرار کنند. بهطور پیشفرض، این ارتباط از طریق لولهای (pipe) است که میتواند پیامها را بین فرآیندها منتقل کند.
Forking و Worker Threads کدامیک را باید انتخاب کرد؟
حالا که با Forking آشنا شدیم، بیایید آن را با Worker Threads، روشی دیگر برای دستیابی به چندوظیفگی در Node.js، مقایسه کنیم.
Forking:
- فرآیندهای جداگانه: هر فرآیند Fork شده مستقل است و فضای حافظه و نمونه موتور V8 مجزایی دارد. این ایزوله بودن باعث میشود که مصرف منابع بیشتر باشد، زیرا هر فرآیند نیاز به تکرار محیط Node.js دارد.
- IPC: ارتباط بین فرآیندها از طریق IPC انجام میشود که کندتر از استفاده از حافظه اشتراکی است.
- مناسب برای وظایف پردازش سنگین: Forking یک انتخاب خوب برای عملیات سنگین است زیرا هر فرآیند فرزند روی نخ جداگانهای اجرا میشود و میتواند از هستههای مختلف پردازنده استفاده کند.
Worker Threads:
- حافظه اشتراکی: برخلاف فرآیندهای Fork شده، Worker Threads حافظه مشترک دارند. این نخها در داخل یک فرآیند واحد اجرا میشوند و از حافظه مشترک استفاده میکنند، که آنها را سبکتر و کارآمدتر از نظر مصرف حافظه میکند.
- عملکرد بهتر: از آنجا که Worker Threads میتوانند از حافظه مشترک استفاده کنند، ارتباط بین نخها سریعتر از فرآیندهای Fork شده است که از IPC استفاده میکنند.
- مناسب برای وظایف I/O و محاسباتی: Worker Threads برای سناریوهایی مناسب هستند که نیاز به اجرای موازی چندین نخ بدون مسدود کردن نخ اصلی دارند، بهویژه در برنامههای I/O.
چندوظیفگی در Node.js از طریق Forking و Worker Threads قابل دستیابی است و هر کدام مزایا و معایب خود را دارند. Forking ایزوله بودن بیشتری را ارائه میدهد و برای وظایف سنگین CPU مناسب است، در حالی که Worker Threads استفاده بهینهتری از حافظه داشته و برای وظایف I/O بهینهتر است. با درک نحوه عملکرد هرکدام، میتوانید بسته به نیاز برنامه خود انتخاب درستی داشته باشید.
چه تصمیم به Fork کردن فرآیندهای جدید بگیرید و چه از Worker Threads استفاده کنید، Node.js ابزارهای قدرتمندی برای بهبود عملکرد و مقیاسپذیری در برنامههای پیچیده ارائه میدهد. حال که با Fork در Node js و برنامه نویسی چند نخی با جاوااسکریپت آشنا شدید می توانید از دیگر مقالات سایت Evolearn دیدن کنید.