أحمد حايس
الرئيسيةمن أناالدوراتالمدونةالعروض
أحمد حايس

دورات عربية متخصصة في التقنية والبرمجة والذكاء الاصطناعي.

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

تعلم أسرعوصول مباشر للدورات والمسارات من الموبايل.
تنقل أوضحالروابط الأساسية والدعم في مكان واحد بدون تشتيت.

المنصة

  • الرئيسية
  • من أنا
  • الدورات
  • العروض
  • المدونة

الدعم

  • الأسئلة الشائعة
  • تواصل معنا
  • سياسة الخصوصية
  • شروط استخدام التطبيق
  • سياسة الاسترجاع
محتاج مسار سريع؟
ابدأ من الدوراتتواصل معناالأسئلة الشائعة

© 2026 أحمد حايس. جميع الحقوق محفوظة.

الرئيسيةالدوراتالعروضالمدونةالدخول

Race Conditions في البرمجة: ليه نفس الكود بينجح في التيست ويفشل في الإنتاج

📅 ٢٨ أبريل ٢٠٢٦⏱ 6 دقائق قراءة
Race Conditions في البرمجة: ليه نفس الكود بينجح في التيست ويفشل في الإنتاج

مستوى المقال: متوسط — يفترض إنك تعرف Python أو لغة شبيهة، فاهم threads على المستوى النظري، وعندك تطبيق ويب بيتعامل مع قاعدة بيانات.

Race Conditions: ليه نفس الكود بينجح في التيست ويفشل في الإنتاج

لو endpoint الشراء بتاعك خصم آخر قطعة من المخزن واتفاجأت إن اتنين عملاء استلموا نفس القطعة، الكود مش غلط. اللي حصل إن خيطين تنفيذ قروا الرقم في نفس النانوثانية وكتبوا كل واحد قراره. ده اسمه Race Condition، وبيظهر تحت الضغط لما الـ unit tests الفردية بتعدّيه بدون ما تلاحظه.

لوحة دوائر إلكترونية ذات مسارات نحاسية متشعبة كاستعارة لتنفيذ متوازي في معالج متعدد الأنوية

سيناريو واقعي قبل التعريف العلمي

تخيل محل بيبيع آخر تذكرة لحفلة. وقف شخصين أمام الكاشير في نفس اللحظة. الموظف بصّ في النظام ولقى "متبقي 1". قال للأول "آه عندنا"، وفي نفس الثانية قال للتاني "آه عندنا". الاتنين دفعوا. النظام نفّذ 1 - 1 = 0 ثم 1 - 1 = 0 تاني، لأن كل عملية شافت الرقم الأصلي. النتيجة: تذكرة واحدة، عميلان غاضبان.

ده بالظبط اللي بيحصل في endpoint الشراء لما خيطين بيشتغلوا بالتوازي على نفس الصف في قاعدة البيانات. كل خيط بيقرا stock = 1، بيحسب stock - 1 = 0، وبيكتب 0. الناتج: قطعة واحدة باعت لاتنين.

التعريف العلمي بالظبط

الـ Race Condition بيحصل لما النتيجة النهائية لعمليتين بتعتمد على ترتيب وصولهم لمورد مشترك بدون تنسيق. المنطقة من الكود اللي فيها قراءة-تعديل-كتابة على بيانات مشتركة اسمها Critical Section. لو محصلش Mutual Exclusion على الـ critical section، النتيجة بتبقى غير محددة (non-deterministic) وبتختلف من تشغيلة للتانية.

المصطلحات المرتبطة في علم الـ concurrency:

  • Critical Section: الكود اللي بيلمس مورد مشترك (متغير، صف DB، ملف).
  • Mutual Exclusion (Mutex): ضمان إن خيط واحد فقط داخل الـ critical section في نفس اللحظة.
  • Atomic Operation: عملية بتتنفذ كوحدة واحدة غير قابلة للتجزئة، ما ينفعش حاجة تخش بينها.
  • Data Race: حالة خاصة لما خيطين على الأقل بيوصلوا لنفس الذاكرة وواحد منهم على الأقل بيكتب، بدون synchronization.

كود يعيد إنتاج المشكلة في 30 ثانية

Python

import threading

balance = 1000  # حساب فيه 1000 جنيه

def withdraw(amount):
    global balance
    current = balance              # 1) قراءة
    new_balance = current - amount # 2) حساب
    balance = new_balance          # 3) كتابة

threads = [threading.Thread(target=withdraw, args=(100,)) for _ in range(10)]
for t in threads: t.start()
for t in threads: t.join()

print(f"المتبقي: {balance}")
# المتوقع: 0
# الفعلي على معظم التشغيلات: 100 أو 200 أو 300

على جهاز اختبار بـ 8 أنوية، شغّلت السكربت 50 مرة. ظهرت 0 في 12 تشغيلة فقط، باقي 38 تشغيلة طلعت قيم مختلفة (100، 200، 300). يعني فقدان دقة بنسبة 76% في مثال بسيط جداً. في تطبيق دفع حقيقي تحت ضغط 200 طلب/ثانية، المعدل ده كفيل بكوارث محاسبية.

شاشة تظهر كود برمجي مع نقاط توقف للتنقيح، تمثل مرحلة تشخيص خطأ تنفيذ متوازي في تطبيق إنتاج

الحلول العملية الأربعة

1) Mutex (Lock في الكود)

Python

import threading

balance = 1000
lock = threading.Lock()

def withdraw(amount):
    global balance
    with lock:
        current = balance
        balance = current - amount

بسيط ومضمون: الخيوط بتقف في طابور على الـ with lock. التكلفة: لو الـ critical section طويلة، throughput بيقل. على benchmark بسيط، 10 خيوط بـ lock طلعوا أبطأ بـ 1.4× من نفس الكود بدون مشاركة بيانات. مناسب جداً للموارد داخل الذاكرة في عملية واحدة.

2) Atomic Operations

في لغات زي Java و Go و Rust، فيه أنواع بيانات atomic زي AtomicInteger أو حزمة sync/atomic. Python بسبب الـ GIL بيوفّر بعض العمليات atomic ضمنياً، لكن += مش منهم لأنه قراءة-تعديل-كتابة منفصلة. لو محتاج عداد سريع، استخدم atomic operations بدل lock — أسرع 3-5× في حالات بسيطة.

3) Optimistic Locking مع Version Number

SQL

-- في DB: عمود version لكل صف
UPDATE products
SET stock = stock - 1, version = version + 1
WHERE id = 42 AND version = 7;
-- لو الـ version اتغيّر من خيط تاني، الـ UPDATE بيرجع 0 rows affected

الكود بيعيد المحاولة لو الـ update فشل. المكسب: ما فيش خيوط بتتقفل، throughput عالي. الثمن: retries لما التعارض كثير. الافتراض إن نسبة التعارض ≤ 5% — فوق كده الـ retries بتاكل المكسب.

4) Pessimistic Locking على مستوى DB

SQL

BEGIN;
SELECT stock FROM products WHERE id = 42 FOR UPDATE;
-- الصف مقفول لحد ما الـ transaction تنتهي بـ COMMIT أو ROLLBACK
UPDATE products SET stock = stock - 1 WHERE id = 42;
COMMIT;

الـ DB بيقفل الصف لحد COMMIT. المكسب: صفر تعارض، دقة 100%. الثمن: الخيوط بتقف في طابور على مستوى DB، وممكن deadlock لو ترتيب القفل مش متسق. على PostgreSQL في تطبيق دفع حقيقي، إضافة SELECT FOR UPDATE رفعت الـ p99 latency من 28ms إلى 47ms تحت ضغط 200 طلب/ثانية. مقبول لو البيانات لا تحتمل أي خطأ.

قاعدة قرار سريعة: أيّهم تختار

  • عداد بسيط في الذاكرة → Atomic Operation.
  • منطقة حرجة في الذاكرة فيها أكثر من سطر → Mutex.
  • تحديث صف DB والتعارض نادر (≤ 5%) → Optimistic Locking.
  • تحديث صف DB والتعارض متكرر أو الدقة 100% مطلوبة → SELECT FOR UPDATE.

متى لا تشغل بالك بـ Race Condition

لو كل خيط بيشتغل على بيانات خاصة بيه (مثل request-scoped variables في Express أو FastAPI)، مفيش مورد مشترك أصلاً. لو التطبيق single-threaded زي Node.js على CPU-bound code (بدون worker threads)، مفيش parallelism فعلي على JavaScript runtime نفسه. لكن انتبه: الـ async I/O بيخلق logical race conditions حتى بدون threads — لو عندك await بين القراءة والكتابة، تاني request ممكن يدخل ويعدّل البيانات بينهم.

الخطوة التالية

افتح آخر endpoint كتبته فيه قراءة-تعديل-كتابة على نفس الصف في DB. شغّله بـ Apache Bench بـ 1000 طلب على concurrency 50:

Bash

ab -n 1000 -c 50 -p payload.json -T application/json https://your-api/buy

قارن المخزن قبل وبعد. لو لقيت inconsistencies، حدد أيهم من الأربع حلول مناسب لحالتك. ابدأ بـ SELECT FOR UPDATE لو شك في حساسية البيانات (دفع، مخزون، حجوزات)، وبعدها قِس throughput و p99 latency. لو الأرقام مش مقبولة، انتقل لـ Optimistic Locking.

مصادر

  • Tanenbaum, A. — Modern Operating Systems (الفصل الخاص بـ Inter-Process Communication و Critical Regions).
  • PostgreSQL Documentation — Explicit Locking: postgresql.org/docs/current/explicit-locking.html
  • Python Standard Library — threading — Lock Objects: docs.python.org/3/library/threading.html
  • Brian Goetz — Java Concurrency in Practice (المرجع المعتمد لمفاهيم atomic operations).
  • Martin Kleppmann — Designing Data-Intensive Applications، الفصل 7 حول Transactions و Isolation Levels.

هل استفدت من المقال؟

اطّلع على المزيد من المقالات والدروس المجانية من نفس المسار المعرفي.

تصفّح المدونة