ما معنى الذاكرة المشتركة (Shared Memory) في الخوارزميات وهياكل البيانات؟

شروحات تقنية

الذاكرة المشتركة (Shared Memory) في سياق الخوارزميات وهياكل البيانات تعني ببساطة: منطقة واحدة من ذاكرة الحاسوب تستطيع أكثر من خيط (Thread) أو عملية (Process) قراءتها والكتابة فيها في الوقت نفسه. فبدلاً من أن يحتفظ كل خيط بنسخته الخاصة من البيانات ثم يرسلها للآخر، يضع الجميع أيديهم على المتغيّر ذاته في العنوان ذاته من الذاكرة. هذه الفكرة هي الأساس الذي تُبنى عليه الخوارزميات المتوازية التي تقسّم العمل على عدة أنوية (Cores) لتنتهي أسرع.

لكن هذه المشاركة نفسها هي مصدر أصعب المشكلات في البرمجة المتوازية، ولهذا يهتم بها مصممو الخوارزميات كثيرًا. لنفصّل الأمر خطوة بخطوة.

الفكرة الأساسية: عنوان واحد يصل إليه الجميع

تخيّل مصفوفة أرقام مخزّنة في الذاكرة. في البرمجة التسلسلية العادية، خيط واحد يمرّ على عناصرها. في النموذج المتوازي القائم على الذاكرة المشتركة، تُنشئ أربعة خيوط مثلاً، ويأخذ كل خيط ربع المصفوفة، لكن جميعها تعمل على المصفوفة نفسها الموجودة في مكان واحد. لا يوجد نسخ ولا نقل للبيانات، وهذا هو مصدر السرعة.

هذا النموذج شائع داخل البرنامج الواحد عبر الخيوط التي تتشارك مساحة عنونة العملية (Address Space)، وكذلك بين عمليات منفصلة عبر آليات مثل ذاكرة mmap المشتركة أو مقاطع الذاكرة المشتركة في نظام التشغيل.

لماذا تهم الذاكرة المشتركة في تصميم الخوارزميات؟

في تحليل الخوارزميات المتوازية يوجد نموذج نظري شهير اسمه PRAM (اختصار Parallel Random Access Machine)، وهو ببساطة معالجات متعددة تتشارك ذاكرة واحدة كبيرة يصل إليها كل معالج في زمن ثابت. هذا النموذج هو الإطار الذي يُدرّس فيه الطلاب كيفية تصميم خوارزميات الفرز والبحث والجمع (Prefix Sum) بشكل متوازٍ.

الأمثلة العملية كثيرة: الفرز المتوازي، ضرب المصفوفات، خوارزميات الرسوم البيانية (Graphs)، والحوسبة العلمية عالية الأداء (HPC). القاسم المشترك بينها أن الخيوط تحتاج لرؤية بيانات بعضها بعضًا بسرعة، والذاكرة المشتركة توفّر ذلك دون تكلفة النسخ.

الذاكرة المشتركة مقابل تمرير الرسائل

ليست الذاكرة المشتركة الطريقة الوحيدة للبرمجة المتوازية. البديل الرئيسي هو تمرير الرسائل (Message Passing / الذاكرة الموزّعة)، حيث تملك كل عملية ذاكرتها الخاصة وتتبادل البيانات بإرسال رسائل صريحة. الجدول التالي يوضّح الفرق:

المعيارالذاكرة المشتركةتمرير الرسائل
كيفية تبادل البياناتالقراءة/الكتابة في عنوان مشتركإرسال واستقبال رسائل صريحة
السرعة داخل الجهازعالية جدًا (لا نسخ)أبطأ بسبب النسخ والتغليف
سهولة الكتابةأسهل بداية، أصعب في الصيانةأوضح لكنه أكثر إسهابًا
الخطر الأساسيحالات التسابق والجمودالأخطاء تظهر بشكل أقل خفاءً
مناسب لـالأنوية المتعددة داخل جهاز واحدالعناقيد والأنظمة الموزّعة عبر الشبكة

القاعدة العملية: الذاكرة المشتركة أنسب داخل جهاز واحد متعدد الأنوية، بينما يتفوّق تمرير الرسائل عندما توزَّع المهام على أجهزة مختلفة عبر الشبكة.

المشكلة الحقيقية: حالة التسابق (Race Condition)

هنا يكمن التحدي الأكبر. إذا حاول خيطان زيادة العدّاد ذاته counter = counter + 1 في اللحظة نفسها، فقد يقرأ كلاهما القيمة القديمة نفسها (مثلاً 5)، ويكتب كلاهما 6، فتضيع زيادة كاملة والنتيجة الصحيحة كانت يجب أن تكون 7. هذا هو ما يُسمّى حالة التسابق، وهي أخطر ما في الذاكرة المشتركة لأنها متقطّعة وقد لا تظهر إلا تحت الضغط، فيصعب تكرارها أثناء التصحيح.

المشكلة الثانية هي الجمود (Deadlock): خيطان ينتظر كل منهما مورداً يمسك به الآخر، فيتوقفان إلى الأبد.

كيف تحمي الذاكرة المشتركة؟

الهدف من أدوات الحماية هو ضمان أن العمليات الحسّاسة (Critical Section) تجري بلا تداخل. أهم الأدوات:

  1. القفل المتبادل (Mutex): قفل يسمح لخيط واحد فقط بالدخول إلى المنطقة الحرجة، وينتظر الباقون دورهم.
  2. السيمافور (Semaphore): عدّاد يسمح لعدد محدود من الخيوط بالوصول في آن واحد.
  3. العمليات الذرية (Atomic Operations): تعليمات مثل الزيادة الذرية تُنفَّذ كوحدة واحدة غير قابلة للتجزئة، وهي أسرع من الأقفال للحالات البسيطة كالعدّادات.
  4. حواجز التزامن (Barriers): تجبر كل الخيوط على الانتظار حتى تصل جميعها إلى نقطة معيّنة قبل المتابعة، وهي شائعة في الخوارزميات المرحلية.

خطأ شائع يقع فيه المبتدئون

الاعتقاد بأن القفل حول كل شيء هو الحل. الإفراط في الأقفال يقتل الفائدة من التوازي كله، لأن الخيوط تقضي وقتها في الانتظار بدل العمل، فتصبح الخوارزمية «المتوازية» أبطأ من التسلسلية أحيانًا. القاعدة الذهبية: اجعل المنطقة المحمية (Critical Section) أصغر ما يمكن، وفضّل العمليات الذرية أو الهياكل غير القافلة (Lock-free) حين يكون ذلك ممكنًا.

الأسئلة الشائعة

هل الذاكرة المشتركة هي نفسها ذاكرة الوصول العشوائي (RAM)؟ لا. الـ RAM هي العتاد الفيزيائي، أما الذاكرة المشتركة فهي مفهوم برمجي يصف منطقة من هذه الذاكرة يُسمح لعدة خيوط أو عمليات بالوصول إليها معًا.

ما الفرق بين الخيط والعملية في هذا السياق؟ خيوط العملية الواحدة تتشارك الذاكرة تلقائيًا لأنها ضمن مساحة العنونة نفسها، بينما تحتاج العمليات المنفصلة إلى ترتيب صريح من نظام التشغيل لإنشاء مقطع ذاكرة مشترك بينها.

متى أفضّل تمرير الرسائل على الذاكرة المشتركة؟ عندما يمتد الحساب على أكثر من جهاز، أو حين تريد عزلاً أوضح بين المهام يقلّل من أخطاء التزامن الخفية، حتى لو كان ذلك على حساب بعض السرعة.

هل تخلّصني اللغات الحديثة من هذه المشكلات؟ تخفّفها فقط. لغات مثل Rust تمنع فئات من أخطاء التسابق أثناء الترجمة عبر نظام الملكية، لكنها لا تلغي حاجتك لفهم التزامن؛ يبقى تصميم الخوارزمية المتوازية مسؤوليتك.