مقدمة: الخرافات والواقع حول تعدد المهام في JavaScript
يرتبط JavaScript تقليديًا بنموذج التنفيذ أحادي الخيط. يعتبر العديد من المطورين، خاصة القادمين من عالم C++ أو Java، هذا قيدًا. ومع ذلك، فإن النظام البيئي الحديث لـ Node.js يقدم أدوات قوية للعمل مع التزامن، والأهم من ذلك، مع تعدد المهام الحقيقي. في هذه المقالة، سنشرح كيفية عمل Event Loop، ومتى نستخدم Worker Threads، وكيف نبني التطبيقات عالية التحميل بشكل صحيح.
على عكس تعدد المهام التقليدي، حيث يكون لكل خيط مكدس خاص به ويمكن تنفيذه بالتوازي على نوى معالج مختلفة، يستخدم JavaScript في المتصفح و Node.js نموذج التزامن القائم على الأحداث. ومع ذلك، مع ظهور وحدة worker_threads في Node.js، حصلنا على إمكانية التوازي الحقيقي.
الجزء الرئيسي
1. Event Loop: قلب التزامن في Node.js
Event Loop هي آلية تسمح لـ Node.js بتنفيذ عمليات الإدخال والإخراج (I/O) غير المحظورة، على الرغم من أن JavaScript أحادي الخيط. يقوم باستمرار بفحص قائمة المهام وتنفيذها عند ورودها.
المراحل الرئيسية لـ Event Loop:
- Timers — تنفيذ دوال الاسترجاع المجدولة عبر
setTimeout()وsetInterval(). - I/O callbacks — معالجة عمليات الإدخال والإخراج المكتملة.
- Idle, prepare — مراحل داخلية لـ Node.js.
- Poll — الحصول على أحداث I/O جديدة وتنفيذ دوال الاسترجاع الخاصة بها.
- Check — تنفيذ دوال الاسترجاع لـ
setImmediate(). - Close callbacks — معالجة أحداث الإغلاق (مثل
socket.on('close', ...)).
مثال على عمل Event Loop:
const fs = require('fs');
console.log('بداية البرنامج');
setTimeout(() => { console.log('تم تشغيل المؤقت');}, 0);
setImmediate(() => { console.log('تم تنفيذ setImmediate');});
fs.readFile(__filename, () => { console.log('تم قراءة الملف');});
console.log('نهاية البرنامج');
// الإخراج:// بداية البرنامج// نهاية البرنامج// تم تنفيذ setImmediate// تم تشغيل المؤقت// تم قراءة الملفمن المهم أن نفهم: Event Loop لا يجعل الكود متعدد الخيوط،他只是 يوزع الوقت بكفاءة بين المهام. إذا استغرقت مهمة واحدة وقتًا طويلاً على وحدة المعالجة المركزية (CPU) (مثل حلقة معقدة)، فسيتم حظر Event Loop بالكامل.
2. مشكلة الحظر والحل: Worker Threads
بالنسبة للمهام كثيفة الاستخدام لوحدة المعالجة المركزية (CPU) (التشفير، معالجة الصور، تحليل البيانات الكبيرة)، فإن Event Loop غير مناسب — سيؤدي إلى حظر الخادم بأكمله. هنا تأتي Worker Threads للإنقاذ.
Worker Threads هي وحدة Node.js تسمح بإنشاء خيوط حقيقية لنظام التشغيل. كل عامل لديه مثيل خاص به من V8 و Event Loop، مما يسمح بتنفيذ الكود بالتوازي.
مثال أساسي لاستخدام Worker Threads:
// main.jsconst { Worker } = require('worker_threads');
function runWorker(workerData) { return new Promise((resolve, reject) => { const worker = new Worker('./worker.js', { workerData }); worker.on('message', resolve); worker.on('error', reject); worker.on('exit', (code) => { if (code !== 0) { reject(new Error(`توقف العامل بالرمز ${code}`)); } }); });}
async function main() { console.log('جارٍ تشغيل العامل...'); const result = await runWorker(42); console.log('النتيجة:', result);}
main().catch(console.error);
// worker.jsconst { parentPort, workerData } = require('worker_threads');
// مهمة كثيفة الاستخدام لوحدة المعالجة المركزيةfunction fibonacci(n) { if (n < 2) return n; return fibonacci(n - 1) + fibonacci(n - 2);}
const result = fibonacci(workerData);parentPort.postMessage(result);في هذا المثال، يتم حساب رقم فيبوناتشي في عامل منفصل، مما لا يمنع الخيط الرئيسي من معالجة الطلبات الأخرى. هذا هو المفتاح لبناء تطبيقات عالية الأداء في Node.js.
3. مقارنة: Event Loop مقابل Worker Threads
دعنا نلخص الاختلافات الرئيسية بين النهجين:
| الميزة | Event Loop | Worker Threads |
|---|---|---|
| نموذج التنفيذ | أحادي الخيط، غير متزامن | متعدد الخيوط، متوازي |
| الذاكرة المشتركة | لا | نعم (عبر SharedArrayBuffer) |
| حالات الاستخدام | عمليات I/O، طلبات الشبكة | المهام كثيفة الاستخدام لوحدة المعالجة المركزية |
| التعقيد | منخفض | متوسط إلى مرتفع |
الخلاصة
تعدد المهام في JavaScript ليس خرافة، ولكنه يتطلب الفهم الصحيح للأدوات المتاحة. يعد Event Loop مثاليًا لمعظم مهام الويب النموذجية، بينما تفتح Worker Threads إمكانيات للحوسبة الحقيقية المتوازية. المفتاح هو اختيار الأداة المناسبة للمهمة: استخدم Event Loop لعمليات I/O غير المتزامنة، و Worker Threads للحسابات الثقيلة. بهذه الطريقة فقط يمكنك بناء تطبيقات Node.js قابلة للتطوير وعالية الأداء.
```