التزامن وتعدد الخيوط في JavaScript: دليل شامل

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

التزامن وتعدد الخيوط في JavaScript: كيفية جعل الكود يعمل بشكل أسرع



كانت JavaScript تاريخياً لغة ذات خيط واحد (single-threaded)، لكن هذا لا يعني أنها لا تستطيع معالجة مهام متعددة في وقت واحد. في Node.js الحديث والمتصفحات، توجد آليات قوية للعمل مع التزامن (Concurrency) وتعدد الخيوط (Multithreading). فهم هذه المفاهيم ضروري جداً لبناء تطبيقات عالية الأداء من جهة الخادم والعميل.



في هذه المقالة، سنشرح الفرق بين التزامن والتوازي (Parallelism)، وكيف يعمل Event Loop، ومتى نستخدم Worker Threads و Web Workers، وسنستعرض أمثلة عملية على Node.js.



1. Event Loop والإدخال/الإخراج غير المحظور (Non-blocking I/O)



أساس التزامن في JavaScript هو Event Loop (حلقة الأحداث). هذه آلية تسمح بتنفيذ عمليات غير متزامنة (مثل قراءة الملفات، طلبات قاعدة البيانات) دون حظر الخيط الرئيسي.



تخيل طابوراً: يأخذ Event Loop مهمة من الطابور (call stack)، وينفذها، وإذا صادف عملية غير متزامنة (مثل setTimeout أو fetch)، يقوم بتمريرها إلى Web API (في المتصفح) أو libuv (في Node.js). بعد اكتمال العملية، يتم وضع دالة الاستدعاء (callback) الخاصة بها في طابور المهام (task queue). يتحقق Event Loop باستمرار مما إذا كان المكدس (stack) فارغاً، وإذا كان فارغاً، يأخذ المهمة التالية من الطابور.



مثال على كود غير متزامن:



const fs = require('fs');

console.log('البداية');

fs.readFile('example.txt', 'utf8', (err, data) => { if (err) throw err; console.log('تم قراءة الملف:', data);});

console.log('النهاية');

// الإخراج:// البداية// النهاية// تم قراءة الملف: ...


لاحظ: console.log('النهاية') يتم تنفيذه قبل قراءة الملف. هذا هو الإدخال/الإخراج غير المحظور — الخيط الرئيسي لا ينتظر اكتمال العملية، بل يواصل التنفيذ.



2. Web Workers و Worker Threads: تعدد خيوط حقيقي



على الرغم من أن Event Loop يتعامل بشكل ممتاز مع عمليات الإدخال/الإخراج (I/O)، إلا أنه لا يساعد في تنفيذ العمليات الحسابية "الثقيلة" (معالجة الصور، التشفير، الفرز المعقد). إذا قمت بتشغيل مثل هذا الكود في الخيط الرئيسي، فإن الواجهة بأكملها (أو الخادم) ستتوقف (تتجمد) حتى اكتمال الحسابات.



لحل هذه المشكلة، يوجد Web Workers (في المتصفح) و Worker Threads (في Node.js). تسمح هذه بتشغيل كود JavaScript في خيوط منفصلة من نظام التشغيل.



مثال مع Worker Threads في Node.js



لنقم بإنشاء ملف worker.js:



const { parentPort } = require('worker_threads');

// مهمة ثقيلة: حساب مجموع الأرقامlet sum = 0;for (let i = 0; i < 1e9; i++) { sum += i;}

// إرسال النتيجة مرة أخرى إلى الخيط الرئيسيparentPort.postMessage(sum);


الملف الرئيسي main.js:



const { Worker } = require('worker_threads');

console.log('الخيط الرئيسي: تشغيل العامل');

const worker = new Worker('./worker.js');

worker.on('message', (result) => { console.log('النتيجة من العامل:', result);});

worker.on('error', (err) => { console.error('خطأ في العامل:', err);});

worker.on('exit', (code) => { if (code !== 0) { console.error(`العامل انتهى بالكود ${code}`); }});

console.log('الخيط الرئيسي: نواصل العمل...');

// الإخراج:// الخيط الرئيسي: تشغيل العامل// الخيط الرئيسي: نواصل العمل...// النتيجة من العامل: 499999999500000000


كما ترى، الخيط الرئيسي لا يتم حظره، بينما يقوم العامل بتنفيذ الحسابات في الخلفية. هذا هو تعدد الخيوط الحقيقي.



3. Async/Await و Promise: إدارة التزامن



تقدم JavaScript الحديثة طرقاً أنيقة للعمل مع الكود غير المتزامن — async/await و Promise. هذه لا تنشئ خيوطاً جديدة، ول

المدونات

توصيات الكتب