العودة للدورة|مقابلات مهندس الخدمات الخلفية: إتقان قواعد البيانات وواجهات البرمجة والأنظمة الموزعة
معمل

مجدول المهام المتزامن

25 دقيقة
متوسط
3 المحاولات المجانية

التعليمات

الهدف

ابنِ مجدول مهام متزامن يدير العمل بأمان عبر عدة خيوط عاملة. هذا التمرين يختبر فهمك لأمان الخيوط وأنماط المنتج-المستهلك وطوابير الأولوية والإيقاف الرشيق — جميعها مواضيع حرجة في مقابلات هندسة الخدمات الخلفية.

المتطلبات

1. طابور محدود آمن للخيوط (منتج-مستهلك)

  • نفّذ TaskQueue بسعة قصوى قابلة للتكوين (مثلاً 100 مهمة)
  • submit(task): يحجب إذا كان الطابور ممتلئًا (ضغط خلفي). يُرجع false إذا كان المجدول يُوقف
  • take(): يحجب إذا كان الطابور فارغًا. يُرجع None/null عندما يُوقف المجدول ويُفرغ الطابور
  • يجب أن يكون آمنًا للوصول المتزامن من عدة خيوط منتجة ومستهلكة
  • استخدم مزلاجًا + متغيرات شرطية (ليس انتظارًا نشطًا)

2. الجدولة بالأولوية

  • المهام لها ثلاثة مستويات أولوية: HIGH و MEDIUM و LOW
  • المهام ذات الأولوية الأعلى يجب أن تُخرج من الطابور قبل المهام ذات الأولوية الأدنى
  • المهام ضمن نفس مستوى الأولوية يجب أن تُعالج بترتيب FIFO
  • هيكل البيانات الأساسي يجب أن يدعم إدراج O(log n) واستخراج O(log n)

3. مجمع العمال

  • أنشئ مجمعًا من N خيوط/goroutines عاملة تستهلك المهام من الطابور
  • كل عامل يكرر: خذ مهمة من الطابور، نفذها، سجل المقاييس
  • العمال يجب أن يتعاملوا مع حالات الذعر/الاستثناءات في المهام برشاقة بدون تعطيل المجمع بالكامل

4. الإيقاف الرشيق

  • shutdown(): توقف عن قبول مهام جديدة، لكن أفرغ جميع المهام المتبقية في الطابور
  • العمال ينهون مهمتهم الحالية ويعالجون جميع المهام المُصفوفة قبل الخروج
  • shutdown() يجب أن يحجب المستدعي حتى يكتمل جميع العمال
  • لا يجب إسقاط أو ترك أي مهام بدون معالجة بعد اكتمال الإيقاف

5. جمع المقاييس

  • تتبع هذه المقاييس بطريقة آمنة للخيوط:
    • الإنتاجية: إجمالي المهام المكتملة
    • متوسط زمن الاستجابة: متوسط الوقت من تقديم المهمة إلى اكتمالها
    • عمق الطابور: العدد الحالي للمهام المنتظرة في الطابور
    • عدد الرفض: عدد المهام المرفوضة (المقدمة بعد الإيقاف)
  • وفّر طريقة get_metrics() تُرجع لقطة من جميع المقاييس

قيود التصميم

  • لا يجوز استخدام طوابير متزامنة عالية المستوى خاصة باللغة (مثلاً PriorityBlockingQueue في Java، queue.PriorityQueue في Python، قنوات Go كطابور أساسي). ابنِ التزامن بنفسك باستخدام المزالج والمتغيرات الشرطية لإظهار الفهم.
  • يجوز استخدام العمليات الذرية للعدادات البسيطة.
  • يجوز استخدام هيكل بيانات كومة/طابور أولوية للترتيب — فقط غلّفه بتزامنك الخاص.

هياكل البيانات

# مستويات أولوية المهام
class Priority:
    HIGH = 0    # رقم أقل = أولوية أعلى
    MEDIUM = 1
    LOW = 2

# مهمة للجدولة
class Task:
    id: str
    priority: Priority
    payload: Callable    # الدالة المراد تنفيذها
    submitted_at: float  # الطابع الزمني عند التقديم

# لقطة المقاييس
class Metrics:
    total_completed: int
    average_latency_ms: float
    current_queue_depth: int
    total_rejected: int

واجهة البرمجة المتوقعة

# أنشئ مجدولاً بـ 4 عمال وحجم طابور أقصى 100
scheduler = TaskScheduler(num_workers=4, max_queue_size=100)

# ابدأ مجمع العمال
scheduler.start()

# قدّم المهام (يحجب إذا كان الطابور ممتلئًا)
scheduler.submit(Task(id="t1", priority=Priority.HIGH, payload=lambda: process_payment()))
scheduler.submit(Task(id="t2", priority=Priority.LOW, payload=lambda: send_email()))
scheduler.submit(Task(id="t3", priority=Priority.MEDIUM, payload=lambda: update_cache()))

# ترتيب المعالجة: t1 (HIGH) -> t3 (MEDIUM) -> t2 (LOW)

# احصل على المقاييس الحالية
metrics = scheduler.get_metrics()
print(f"مكتمل: {metrics.total_completed}, متوسط الزمن: {metrics.average_latency_ms}ms")

# إيقاف رشيق — يُفرغ الطابور، ينتظر العمال لينتهوا
scheduler.shutdown()

استراتيجية القفل (اشرح في التعليقات)

في حلك، أضف تعليقات تشرح:

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

تلميحات

  • استخدم كومة صغرى لطابور الأولوية، مفتاحها (priority, submission_order) للحفاظ على ترتيب FIFO ضمن نفس مستوى الأولوية
  • عداد submission_order (عدد صحيح متزايد) يكسر التعادل ضمن نفس الأولوية
  • استخدم مزلاجًا واحدًا لحماية الطابور + متغير شرطي not_empty للمستهلكين + متغير شرطي not_full للمنتجين
  • للإيقاف الرشيق، عيّن علامة shutting_down، أشر لجميع الخيوط المنتظرة، واجعل العمال يتحققون من العلامة وفراغ الطابور معًا
  • غلّف تنفيذ المهمة في try/except (Python) أو recover (Go) أو try/catch (Java) لمنع تعطل العمال

ما يجب تقديمه

يجب أن يحتوي تقديمك على قسم ملف واحد في المحرر أدناه: ملف Python كامل ينفّذ فئة TaskScheduler.


معايير التقييم

أمان الخيوط: عمليات الطابور (submit, take) محمية بمزلاج. المتغيرات الشرطية مُستخدمة للحجب (ليس الانتظار النشط). بدون حالات سباق على العدادات المشتركة. استراتيجية القفل موثقة في تعليقات تشرح أي قفل يحمي أي حالة.25 نقاط
تنفيذ الطابور: مخزن مؤقت محدود بسعة قصوى قابلة للتكوين. submit() يحجب عندما يكون ممتلئًا (ضغط خلفي باستخدام انتظار متغير شرطي). take() يحجب عندما يكون فارغًا. يستخدم هيكل بيانات قائم على الكومة لعمليات O(log n). المنتجون والمستهلكون يُشيرون لبعضهم بشكل صحيح عبر المتغيرات الشرطية.20 نقاط
الجدولة بالأولوية: ثلاثة مستويات أولوية (HIGH, MEDIUM, LOW) مُنفذة بشكل صحيح. المهام ذات الأولوية الأعلى تُخرج دائمًا قبل الأدنى. ترتيب FIFO محفوظ ضمن نفس مستوى الأولوية باستخدام عداد أحادي. مفتاح الكومة يجمع بشكل صحيح بين الأولوية وترتيب التقديم.20 نقاط
الإيقاف الرشيق: shutdown() يعيّن علامة لرفض التقديمات الجديدة. جميع المهام المُصفوفة تُفرغ وتُعالج قبل خروج العمال. العمال ينهون مهمتهم الحالية. shutdown() يحجب حتى يكتمل جميع العمال (thread join). بدون إسقاط أو ترك مهام بدون معالجة. submit() يُرجع False بعد بدء الإيقاف.15 نقاط
جمع المقاييس: يتتبع total_completed ومتوسط_زمن_الاستجابة (من التقديم إلى الاكتمال) وعمق_الطابور_الحالي وإجمالي_المرفوض بطريقة آمنة للخيوط. get_metrics() تُرجع لقطة متسقة. استثناءات المهام تُلتقط بدون تعطيل العمال، والمهام الفاشلة تنعكس في المقاييس بشكل مناسب.20 نقاط

قائمة التحقق

0/8

حلك

3 محاولات مجانية متبقية