التزامن وتعدد الخيوط في JavaScript: المستوى المتقدم
\n\nتُعتبر JavaScript تقليديًا لغة أحادية الخيط، ومع ذلك فإن النظام البيئي الحديث لـ Node.js يوفر أدوات قوية للعمل مع التزامن وتعدد الخيوط الحقيقي. في هذه المقالة، سنستعرض التقنيات المتقدمة: من تفاصيل Event Loop إلى استخدام Worker Threads و SharedArrayBuffer و Atomics. ستتعلم كيفية توزيع الحمل بكفاءة، وتجنب حالات السباق، وبناء تطبيقات قابلة للتوسع.
\n\n1. Event Loop، المهام الصغيرة والمهام الكبيرة: أساس التزامن
\n\nفهم Event Loop هو مفتاح إدارة التزامن دون حظر. في Node.js، تتكون دورة الأحداث من عدة مراحل: المؤقتات، استدعاءات الإدخال/الإخراج، الخمول، الاستقصاء، الفحص (setImmediate) والإغلاق. من المهم التمييز بين المهام الصغيرة (Promise، process.nextTick) والمهام الكبيرة (setTimeout، setInterval، الإدخال/الإخراج).
\n\nيتم تنفيذ المهام الصغيرة فور انتهاء المرحلة الحالية، قبل المهمة الكبيرة التالية. قد يؤدي ذلك إلى "تجويع" Event Loop إذا تم إنشاء المهام الصغيرة بشكل لا نهائي.
\n\n// مثال: المهام الصغيرة تحجب المهام الكبيرة\nsetTimeout(() => console.log('مهمة كبيرة'), 0);\n\nPromise.resolve().then(() => {\n // مهمة صغيرة لا نهائية\n process.nextTick(() => {\n console.log('مهمة صغيرة');\n // استدعاء متكرر سيحجب Event Loop\n process.nextTick(() => process.nextTick(() => {}));\n });\n});\n// الإخراج: 'مهمة صغيرة' (بشكل لا نهائي)، 'مهمة كبيرة' لن يتم تنفيذها أبدًا\n\nللتحكم المتقدم، استخدم queueMicrotask() و setImmediate(). تذكر: process.nextTick له أولوية أعلى من المهام الصغيرة الخاصة بـ Promise.
2. Worker Threads: تعدد خيوط حقيقي في Node.js
\n\nتتيح وحدة worker_threads تنفيذ JavaScript في خيوط متوازية. كل Worker لديه مثيل V8 خاص به وذاكرة معزولة. يتم تبادل البيانات عبر الرسائل أو SharedArrayBuffer.
إنشاء Worker بسيط:
\n\n// main.js\nconst { Worker } = require('worker_threads');\n\nfunction runWorker(file, data) {\n return new Promise((resolve, reject) => {\n const worker = new Worker(file, { workerData: data });\n worker.on('message', resolve);\n worker.on('error', reject);\n worker.on('exit', (code) => {\n if (code !== 0) reject(new Error(`تم إيقاف Worker بالرمز ${code}`));\n });\n });\n}\n\n// التشغيل\nrunWorker('./worker.js', { iterations: 1e9 })\n .then(result => console.log('النتيجة:', result))\n .catch(err => console.error(err));\n\n// worker.js\nconst { parentPort, workerData } = require('worker_threads');\n\nfunction heavyComputation(n) {\n let sum = 0;\n for (let i = 0; i < n; i++) { sum += Math.sqrt(i); }\n return sum;\n}\n\nparentPort.postMessage(heavyComputation(workerData.iterations));\n\nهام: لا يملك Worker وصولًا إلى DOM، أو الكائنات العامة للعملية (process.env)، أو الوحدات المحملة في الخيط الرئيسي. قم بنقل البيانات القابلة للتسلسل فقط.
\n\nتجمع Worker
\nإنشاء Worker لكل مهمة مكلف. استخدم تجمعًا:
\n\nclass WorkerPool {\n constructor(file, size = 4) {\n this.workers = [];\n this.queue = [];\n for (let i = 0; i < size; i++) {\n const worker = new Worker(file);\n worker.on('message', (result) => this._handleResult(worker, result));\n worker.on('error', (err) => this._handleError(worker, err));\n this.workers.push(worker);\n }\n }\n\n runTask(data) {\n return new Promise((resolve, reject) => {\n this.queue.push({ data, resolve, reject });\n this._processQueue();\n });\n }\n\n _processQueue() {\n if (this.queue.length === 0) return;\n const worker = this.workers.find(w => !w.busy);\n if (!worker) return;\n worker.busy = true;\n const task = this.queue.shift();