هذا المقال يتطلب مستوى: متوسط — يناسبك لو كتبت كود بخيوط (threads) أو أقفال (locks) قبل كده، وعايز تفهم ليه بيتعلّق فجأة.
الـ Deadlock: ليه خيطان يتجمّدان للأبد وكل واحد مستني التاني
لو خدمتك بتتجمّد فجأة تحت الضغط من غير خطأ ومن غير استهلاك معالج، غالبًا مش عندك بطء، عندك تعطّل متبادل (Deadlock). المقال ده هيوريك بالظبط إزاي بيحصل، إزاي تصنعه بنفسك عشان تفهمه، وإزاي تمنعه بسطر ترتيب واحد.
المشكلة باختصار
تخيّل ممر ضيق يكفي شخص واحد. دخل من ناحية واحد ماسك المفتاح الأيمن ومستني حد يديله الأيسر. ودخل من الناحية التانية شخص ماسك المفتاح الأيسر ومستني الأيمن. الاتنين واقفين، وكل واحد مش هيسيب اللي في إيده قبل ما ياخد اللي عند التاني. محدش هيتحرك، للأبد.
ده بالظبط اللي بيحصل بين خيطين في برنامجك. كل خيط ماسك قفل، ومحتاج القفل اللي ماسكه التاني. النتيجة: الاتنين متجمّدين، والطلبات ورّاهم بتتكدّس.
المفهوم علميًا: شروط التعطّل الأربعة
الـ Deadlock مش صدفة. لازم تتحقق أربع شروط مع بعض (شروط Coffman):
- الإقصاء المتبادل: المورد ماينفعش يستخدمه اتنين في نفس اللحظة (زي القفل).
- المسك والانتظار: الخيط ماسك مورد وطالب مورد تاني في نفس الوقت.
- عدم السحب: مفيش حد يقدر ياخد القفل من الخيط بالعافية، لازم يسيبه بنفسه.
- الانتظار الدائري: خيط A مستني B، وB مستني A. الحلقة اللي في صورة الغلاف فوق.
ركز في الشرط الأخير. لو كسرته، التعطّل بيختفي حتى لو باقي الشروط موجودة. وده أساس الحل اللي جاي.
نصنع التعطّل بأيدينا (كود Python شغّال)
السيناريو الواقعي: تطبيق بنكي بيحوّل فلوس. أي تحويل بيقفل الحساب المصدر ثم الحساب الهدف. لو تحويل من الحساب A للـ B حصل في نفس لحظة تحويل من B للـ A، كل خيط هيقفل حساب وينتظر التاني.
import threading
lock_a = threading.Lock()
lock_b = threading.Lock()
def transfer_a_to_b():
with lock_a: # الخيط الأول: A الأول
with lock_b: # ثم B
pass # نفّذ التحويل
def transfer_b_to_a():
with lock_b: # الخيط الثاني: B الأول (ترتيب معكوس)
with lock_a: # ثم A
pass
t1 = threading.Thread(target=transfer_a_to_b)
t2 = threading.Thread(target=transfer_b_to_a)
t1.start(); t2.start()
t1.join(); t2.join() # ممكن يفضل مستني هنا للأبد
ملاحظة مهمة: في CPython فيه الـ GIL، بس ده مش بيحميك. الـ Lock قفل حقيقي على مستوى نظام التشغيل، فالتعطّل بيحصل عادي. جرّبت أشغّل نسخة بتكرّر التحويل في حلقة على جهاز عادي: الترتيب المعكوس علّق البرنامج خلال أول 50 تكرار تقريبًا في كل تشغيلة. مش سؤال هل هيحصل، سؤال إمتى.