التزامن وتعدد الخيوط في JavaScript: المستوى المتقدم
كانت JavaScript تاريخياً لغة أحادية الخيط تعمل ضمن حلقة الأحداث (Event Loop). ومع ذلك، تتطلب التطبيقات الحديثة على Node.js أداءً عالياً عند معالجة المهام كثيفة الاستخدام لوحدة المعالجة المركزية (CPU). في هذه المقالة، سنستعرض الآليات المتقدمة للتزامن وتعدد الخيوط: Worker Threads، SharedArrayBuffer، Atomics، والتجميع. ستتعلم كيفية استخدام المعالجات متعددة النوى بكفاءة وتجنب حظر الخيط الرئيسي.
1. Worker Threads: تعدد خيوط حقيقي في Node.js
ظهرت وحدة worker_threads في Node.js 10.5.0 وتتيح إنشاء خيوط منفصلة بمحرك V8 خاص بها. على عكس التجميع (العمليات)، تشارك Worker Threads الذاكرة ويمكنها تبادل البيانات عبر SharedArrayBuffer.
إنشاء وتشغيل العامل (Worker)
// main.jsconst { Worker } = require('worker_threads');
function runWorker(data) { return new Promise((resolve, reject) => { const worker = new Worker('./worker.js', { workerData: data }); worker.on('message', resolve); worker.on('error', reject); worker.on('exit', (code) => { if (code !== 0) reject(new Error(`Worker stopped with exit code ${code}`)); }); });}
(async () => { const result = await runWorker({ numbers: [1, 2, 3, 4, 5] }); console.log('النتيجة:', result); // { sum: 15 }})();// worker.jsconst { parentPort, workerData } = require('worker_threads');
const sum = workerData.numbers.reduce((acc, val) => acc + val, 0);parentPort.postMessage({ sum });هام: لا تمتلك العمال (Workers) إمكانية الوصول إلى DOM أو الوحدات التي تتطلب الخيط الرئيسي. استخدمها للحسابات الثقيلة، معالجة الصور، أو التشفير.
2. SharedArrayBuffer و Atomics: الذاكرة المشتركة
يتيح SharedArrayBuffer لعدة عمال قراءة وكتابة البيانات في منطقة ذاكرة واحدة. لمزامنة الوصول، يُستخدم كائن Atomics الذي يضمن العمليات الذرية.
مثال: معالجة مصفوفة بالتوازي
// main.jsconst { Worker } = require('worker_threads');const { SharedArrayBuffer } = require('worker_threads');
const buffer = new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * 10);const array = new Int32Array(buffer);
// نملأ المصفوفةfor (let i = 0; i < 10; i++) array[i] = i;
const worker1 = new Worker('./worker_atomics.js', { workerData: { start: 0, end: 5 } });const worker2 = new Worker('./worker_atomics.js', { workerData: { start: 5, end: 10 } });
worker1.on('message', () => console.log('العامل 1 انتهى'));worker2.on('message', () => console.log('العامل 2 انتهى'));// worker_atomics.jsconst { parentPort, workerData } = require('worker_threads');const { Atomics } = require('worker_threads');
const buffer = new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * 10);const array = new Int32Array(buffer);
// زيادة ذرية لكل عنصرfor (let i = workerData.start; i < workerData.end; i++) { Atomics.add(array, i, 10);}
parentPort.postMessage('done');ملاحظة: استخدم Atomics.wait() و Atomics.notify() لمزامنة الخيوط (مشابه للأقفال المتبادلة). هذا أمر بالغ الأهمية لمنع حالات السباق.
3. التجميع: التوسع عبر العمليات
تنشئ وحدة cluster عدة عمليات Node.js (عمال)، كل منها يعالج الطلبات. هذه هي الطريقة القياسية لتوسيع نطاق خوادم HTTP على الأنظمة متعددة النوى.
مثال: خادم HTTP مجمع
const cluster = require('cluster');const http = require('http');const numCPUs = require('os').cpus().length;
if (cluster.isMaster) { console.