الوحدة 5: التزامن والحصر المتبادل

Concurrency: Mutual Exclusion and Synchronization

📚 استنادًا إلى الفصل الخامس من كتاب: Operating Systems: Internals and Design Principles – William Stallings – الإصدار التاسع

أهداف الوحدة

  • فهم أساسيات التزامن بين العمليات والخيوط.
  • إدراك أهمية الحصر المتبادل (Mutual Exclusion).
  • استخدام أدوات التزامن مثل Semaphores وMonitors.
  • معرفة كيفية تمرير الرسائل بين العمليات.
  • تطبيق مفاهيم التزامن لحل مشكلات مشهورة مثل: القراء والكتاب (Readers-Writers).

1️⃣ مبدأ التزامن في أنظمة التشغيل

🔹 التزامن (Concurrency):

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

🔸 أهمية التزامن:

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

🔸 المشكلة:

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

📌 مثال: تخيل أن عمليتين تحاولان تحديث رصيد حساب بنكي في نفس الوقت. إذا قرأت العملية الأولى الرصيد (1000 ريال)، ثم قرأت العملية الثانية نفس الرصيد (1000 ريال)، ثم أضافت الأولى 100 ريال (لتصبح 1100)، ثم أضافت الثانية 50 ريال (لتصبح 1050)، فإن النتيجة النهائية ستكون 1050 بدلاً من 1150، بسبب فقدان تحديث العملية الأولى. هذه تُعرف بـ "حالة السباق" (Race Condition).


2️⃣ الحصر المتبادل (Mutual Exclusion)

🔹 التعريف:

الحصر المتبادل هو مفهوم أساسي في التزامن يضمن عدم تنفيذ أكثر من خيط أو عملية لنفس **القسم الحرج (Critical Section)** في نفس الوقت.

🔹 القسم الحرج (Critical Section):

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

الحلول البرمجية المبكرة للحصر المتبادل:

  • Peterson’s Algorithm: خوارزمية برمجية كلاسيكية لحل مشكلة الحصر المتبادل في بيئة تتكون من خيطين فقط. تضمن هذه الخوارزمية الحصر المتبادل، وتجنب الجمود (Deadlock)، وتجنب المجاعة (Starvation).
  • Dekker’s Algorithm: حل أقدم من Peterson's Algorithm، يعتمد أيضًا على البرمجيات لحل مشكلة الحصر المتبادل لخيطين.

🔗 مصدر خارجي: Peterson’s Algorithm – GeeksForGeeks


3️⃣ الدعم العتادي للتزامن

للتغلب على تعقيدات الحلول البرمجية البحتة وضمان الحصر المتبادل بكفاءة عالية، توفر المعالجات الحديثة أوامر خاصة (تعليمات عتادية) تسمح بإجراء عمليات ذرية (Atomic Operations).

🔹 العمليات الذرية (Atomic Operations):

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

🔸 أمثلة على تعليمات الدعم العتادي:

  • Test and Set (اختبار وتعيين): هذه التعليمة تقوم بعمليتين (اختبار قيمة ذاكرة وتعيينها إلى قيمة جديدة) كعملية ذرية واحدة. تُستخدم عادةً لتنفيذ الأقفال (Locks).
  • Compare and Swap (قارن وبدّل): تقارن قيمة في الذاكرة بقيمة متوقعة. إذا كانت متطابقة، يتم استبدالها بقيمة جديدة. تتم العملية بأكملها كعملية ذرية.
  • Exchange Instruction (تعليمة التبادل): تبادل محتويات سجل (Register) مع محتويات موقع في الذاكرة كعملية ذرية.

📘 مثال على Atomic Instruction: تخيل أن لديك عدادًا مشتركًا. إذا قام خيطان بزيادة العداد في نفس الوقت، بدون عملية ذرية، قد تحدث مشكلة (Race Condition). باستخدام تعليمة ذرية، يضمن المعالج أن عملية القراءة والزيادة والكتابة تتم كوحدة واحدة دون مقاطعة، مما يضمن سلامة العداد.


4️⃣ السيموفور (Semaphore)

السيموفور (Semaphore) هو أداة تزامن قوية ومرنة، تُستخدم للتحكم في الوصول إلى الموارد المشتركة بين العمليات أو الخيوط. يمكن اعتبارها عدادًا أو إشارة تُستخدم لضبط عدد العمليات التي يمكنها الوصول إلى مورد معين في وقت واحد.

🔸 تتكون السيموفور من عمليتين أساسيتين (تُنفذان بشكل ذري):

  • wait(S) أو P(S) (Proberen - "اختبار"):
    • تخفض قيمة السيموفور S بمقدار 1.
    • إذا كانت قيمة S سالبة بعد التخفيض، فهذا يعني أن المورد غير متاح، وتُوضع العملية في قائمة انتظار حتى يصبح المورد متاحًا.
  • signal(S) أو V(S) (Verhogen - "زيادة"):
    • ترفع قيمة السيموفور S بمقدار 1.
    • إذا كانت هناك عمليات تنتظر على هذا السيموفور، يتم إيقاظ واحدة منها (إطلاق سراحها) لتتمكن من الوصول إلى المورد.

🔸 أنواع السيموفور:

  • Binary Semaphore (السيموفور الثنائي): قيمته تكون 0 أو 1 فقط. يعمل بشكل مشابه للقفل (Mutex)، حيث يسمح لعملية واحدة فقط بالوصول إلى القسم الحرج في وقت واحد.
  • Counting Semaphore (السيموفور العدّاد): يمكن أن يأخذ أي قيمة صحيحة غير سالبة. يُستخدم للتحكم في الوصول إلى موارد متعددة النسخ (مثل عدد معين من الطابعات المتاحة).

📌 مثال: تخيل قاعة اجتماعات بها 10 مقاعد. يمكن استخدام Counting Semaphore بقيمة ابتدائية 10. كلما دخل شخص، يتم wait()، وكلما خرج شخص، يتم signal(). إذا امتلأت القاعة (القيمة صفر)، ينتظر الأشخاص الجدد حتى يخرج أحدهم.

🔗 مصدر خارجي: Semaphore Tutorial – TutorialsPoint


5️⃣ المراقبات (Monitors)

المراقبات (Monitors) هي آلية تزامن برمجية عالية المستوى، توفر طريقة منظمة وآمنة للتحكم في الوصول إلى البيانات المشتركة بين الخيوط أو العمليات. تُبسّط المراقبات عملية التزامن بشكل كبير مقارنة بالسيموفور، حيث أنها تُخفي تعقيدات آليات الحصر المتبادل عن المبرمج.

🔸 تتكون المراقبة عادةً من:

  • متغيرات الحالة (Shared Variables): البيانات التي تحتاج إلى حماية من الوصول المتزامن.
  • إجراءات محمية (Protected Procedures): مجموعة من الدوال أو الإجراءات التي يمكنها الوصول إلى متغيرات الحالة. يضمن نظام المراقبة أن خيطًا واحدًا فقط يمكنه تنفيذ أحد هذه الإجراءات في أي لحظة.
  • بنية تنظيم تلقائية للتزامن: المراقبة نفسها تتولى مهمة الحصر المتبادل تلقائيًا، بالإضافة إلى توفير آليات لانتظار وإيقاظ الخيوط (مثل متغيرات الشرط - Condition Variables).

📌 مميزات المراقبات:

  • تمنع التداخل التلقائي: المبرمج لا يحتاج للقلق بشأن وضع الأقفال يدويًا، فالمراقبة تضمن ذلك.
  • أسهل في الاستخدام: تقلل من الأخطاء الشائعة في التزامن مثل الجمود أو حالات السباق.
  • تُستخدم بشكل واسع في لغات البرمجة الحديثة مثل Java (عبر الكلمة المفتاحية synchronized وكائنات Object.wait()/notify()) وC# (عبر الكلمة المفتاحية lock وكائن Monitor).

🔗 مصدر خارجي: Monitors in Java – Baeldung


6️⃣ تمرير الرسائل (Message Passing)

تمرير الرسائل (Message Passing) هي آلية للتواصل والتزامن بين العمليات، تُستخدم بشكل خاص في الأنظمة التي لا تشارك الذاكرة بين العمليات (مثل الأنظمة الموزعة أو النوى المصغرة). بدلاً من الوصول المباشر إلى الذاكرة المشتركة، تتواصل العمليات عن طريق إرسال واستقبال رسائل.

🔸 الشكل العام لعمليات تمرير الرسائل:

  • send(destination, message): لإرسال رسالة إلى عملية مستهدفة.
  • receive(source, message): لاستقبال رسالة من عملية معينة أو من أي عملية.

🔸 مميزات تمرير الرسائل:

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

📌 استخدام في الأنظمة المضمنة والمايكروكرنل: تُستخدم آليات تمرير الرسائل بشكل مكثف في أنظمة التشغيل ذات النواة المصغرة (Microkernels) مثل Minix، حيث تتواصل مكونات نظام التشغيل (التي تعمل كعمليات منفصلة) مع بعضها البعض عبر الرسائل.


7️⃣ مشكلة القراء والكتاب (Readers-Writers Problem)

مشكلة القراء والكتاب هي مشكلة كلاسيكية في التزامن تُستخدم لشرح تحديات إدارة الوصول المتزامن إلى مورد مشترك.

🔹 الوصف:

لدينا مجموعة من العمليات (أو الخيوط) التي ترغب في الوصول إلى قاعدة بيانات أو ملف مشترك. تنقسم هذه العمليات إلى نوعين:

  • القراء (Readers): يمكنهم قراءة البيانات. يمكن لعدة قراء الوصول إلى البيانات في نفس الوقت (التزامن مسموح).
  • الكتاب (Writers): يمكنهم تعديل البيانات. يجب أن يكون الكاتب الوحيد الذي يصل إلى البيانات في أي وقت؛ لا يمكن لأي قارئ أو كاتب آخر الوصول أثناء عملية الكتابة.

🔸 الحل:

يتطلب حل هذه المشكلة تطبيق قواعد الحصر المتبادل بذكاء:

  • السماح لعدة قراء بالدخول إلى القسم الحرج معًا.
  • منع أي كاتب من الدخول إذا كان هناك قارئ واحد على الأقل موجودًا.
  • تأمين الحصر المتبادل بين الكتاب (أي لا يمكن لكاتبين الكتابة في نفس الوقت).
  • ضمان عدم حدوث مجاعة (Starvation)، أي عدم حرمان القراء أو الكتاب من الوصول إلى الأبد.

📘 يتم استخدام Semaphores (أو Monitors) بشكل شائع لحل مشكلة القراء والكتاب، حيث يتم استخدام سيموفور للتحكم في الوصول للكتاب، وسيموفور آخر لإدارة عدد القراء النشطين.


ملخص الوحدة

في هذه الوحدة، استكشفنا بعمق مفهوم **التزامن (Concurrency)** وأهميته في أنظمة التشغيل الحديثة، وتعرفنا على التحديات التي يفرضها، خاصة **حالات السباق**. تعلمنا عن مفهوم **الحصر المتبادل (Mutual Exclusion)** كحل لهذه التحديات، وكيف يتم دعمه من خلال **التعليمات العتادية الذرية**. ثم غصنا في أدوات التزامن البرمجية الأكثر شيوعًا: **السيموفور (Semaphores)** بأنواعها المختلفة و**المراقبات (Monitors)** كآلية عالية المستوى. كما تعرفنا على **تمرير الرسائل (Message Passing)** كبديل لمشاركة الذاكرة، وأخيرًا، طبقنا هذه المفاهيم على **مشكلة القراء والكتاب** الكلاسيكية. فهم هذه الآليات ضروري لبناء أنظمة برمجية قوية وموثوقة في بيئات متعددة المهام.