بنناجح
المرحلة 1: مقدمة: نظام التشغيل بيعمل إيه؟ · الدرس 4 من 6 · 20 دقيقة

لما حاجتين يشتغلوا في نفس الوقت

نظام التشغيل نفسه أول برنامج اضطر يتعامل مع حاجات كتير شغالة مع بعض: Interrupts، و Processes كتير، وكذا Core بيلمسوا نفس الداتا. وأي برنامج بيستخدم Threads بيقابل نفس المشكلة. الدرس ده بيوريك ليه سطر زي count++ ممكن يطلّع رقم غلط، وده كل اللي مرحلة الـ Concurrency هتحله.

في الدرس ده هتتعلم
  • تعرّف الـ Thread وتفرّقه عن الـ Process
  • تفهم ليه count++ مش عملية واحدة
  • تشوف Race Condition خطوة خطوة
  • تعرف الأدوات اللي بتحلها: Atomic Operations و Locks

1. Process و Thread

لحد دلوقتي كل Process ليها Address Space لوحدها، ومش بتشوف ذاكرة غيرها. بس ساعات البرنامج نفسه عايز يعمل كذا حاجة في نفس الوقت على نفس الداتا: سيرفر بيخدم 1000 اتصال، أو برنامج بيعالج صورة على 8 Cores.

Process Thread
الذاكرة Address Space لوحدها مشتركة مع باقي الـ Threads في نفس الـ Process
لو وقع بيقع لوحده ممكن يوقّع الـ Process كلها بكل الـ Threads
التواصل محتاج قناة (Pipe، Socket، Shared Memory) بيقرا ويكتب نفس المتغيرات مباشرة
أمثلة كل php-fpm Worker Process لوحده الـ Threads في Java و Go (Goroutines) وفي MySQL نفسه

الذاكرة المشتركة سريعة ومريحة، وهي نفسها مصدر المشكلة.

2. count++ مش عملية واحدة

متغير count مشترك، وكل Thread بيزوده بواحد مع كل Request. في الكود شكله سطر واحد، بس الـ CPU بيشوفه 3 أوامر:

count++ في الـ CPU
load  reg, [count]     ; هات القيمة من الذاكرة لـ Register
add   reg, 1           ; زوّد واحد على الـ Register
store [count], reg     ; رجّع القيمة للذاكرة

والـ Timer Interrupt (أو Thread تاني على Core تاني) ممكن يدخل بين أي أمرين منهم.

عداد الزيارات بعد زيارتينجرّب بنفسك
في الداتابيزcount في الذاكرة100
Thread 1 (Core 0)⏱Thread 2 (Core 1)
  1. load reg1, [count]reg1 = 1001
  2. 2load reg2, [count]reg2 = 100
  3. add reg1, 1 store [count], reg1كتب 1013
  4. 4add reg2, 1 store [count], reg2كتب 101
▶

دوس ▶ شغّل، أو امشي خطوة خطوة بالأسهم.

أي داتا مشتركة بين حاجتين شغالين في نفس الوقت، وفيه واحد منهم على الأقل بيكتب، محتاجة حماية. سطر واحد في الكود ممكن يبقى كذا خطوة في الـ CPU.

3. ده يخصك إزاي كـ Backend Developer؟

  • PHP (php-fpm): كل Request في Process لوحدها ومفيش ذاكرة مشتركة، فالمشكلة دي مش بتظهر في الذاكرة. بس بتظهر في أي حاجة مشتركة بين الـ Processes: الداتابيز، و Redis، و الملفات. عداد في Redis بـ GET ثم SET = نفس الـ Race بالظبط، والحل INCR (أمر ذري).
  • Node.js: Thread واحد للـ JavaScript، فمفيش أمرين بيتداخلوا في النص. بس أي await في النص بيفتح باب: كود قرا قيمة، وعمل await لحاجة، وبعدين كتب.
  • Go و Java و Laravel Octane: Threads أو Goroutines بذاكرة مشتركة فعلاً. هنا محتاج sync.Mutex أو AtomicInteger أو تتجنب الحالة المشتركة خالص.

مرحلة الـ Concurrency في الكورس بتدخل في الـ Locks من جوه، وإزاي بتتعمل بالـ Hardware، والـ Condition Variables، والـ Deadlocks.

4. أسئلة

فكّر في إجابتك الأول، وبعدين افتح السؤال.

01الفرق بين Process و Thread؟

الـ Process ليها Address Space لوحدها ومعزولة عن غيرها. الـ Threads جوه نفس الـ Process بيشتركوا في الذاكرة والملفات المفتوحة، وكل Thread ليه Stack و Registers خاصين بيه. الـ Threads أخف وأسرع في التواصل، بس محتاجين حماية للداتا المشتركة.

02ليه count++ ممكن يطلّع نتيجة غلط مع Threads؟

لأنه 3 أوامر في الـ CPU: load ثم add ثم store. لو Thread تاني قرا القيمة قبل ما الأول يكتب، الاتنين هيكتبوا نفس القيمة وزيادة هتضيع.

03يعني إيه Race Condition؟ وإزاي تحلها؟

لما النتيجة بتعتمد على ترتيب تنفيذ الـ Threads بالظبط. بتتحل إنك تخلّي الجزء الحساس (Critical Section) يتنفذ من Thread واحد في الوقت، بـ Lock (Mutex)، أو تستخدم Atomic Operation زي INCR في Redis أو AtomicInteger.

04PHP مفيهوش Threads. يبقى مفيش Race Conditions؟

لأ. كل Request في Process لوحدها، بس كلهم بيشتركوا في الداتابيز و Redis والملفات. قراية قيمة وبعدين كتابتها (Read-Modify-Write) على أي مورد مشترك فيها نفس الـ Race، وحلها عمليات ذرية (UPDATE x = x + 1، INCR) أو Locks.

علّم عليه لما تقدر تجاوب أسئلة الإنترفيو من غير ما تبص.