مستوى المقال: متوسط. الافتراض إنك تعرف يعني إيه thread وقفل (lock) أساسي. لو لسه جديد على الـ threads، المثال الأول في المقال هيوصّلك الفكرة قبل الكود.
في آخر المقال هتعرف تكتشف جمود (deadlock) بيجمّد خدمتك من غير ما يطلع ولا رسالة خطأ، وتصلحه بقاعدة واحدة بسيطة. الوقت المتوقع للقراءة حوالي 7 دقايق.
الجمود (Deadlock): لما خيطان يستنّيا بعض للأبد
ركز في الفرق ده: البرنامج اللي بيقع بيديك رسالة خطأ وستاك تريس. البرنامج اللي فيه جمود مش بيقع، بس مش بيتحرك. بيفضل واقف مستني حاجة مش هتيجي أبدًا. ده أخطر، لأنه بيعدي في التستات وبيموت في الإنتاج.
المشكلة باختصار
تخيّل أخوين في مطبخ. الأول ماسك السكينة ومستني اللوح عشان يقطّع. التاني ماسك اللوح ومستني السكينة. كل واحد مش هيسيب اللي في إيده قبل ما ياخد اللي ناقصه. النتيجة: الاتنين واقفين، والأكل مش هيتعمل. مفيش حد بيصرخ، بس المطبخ متجمّد.
ده بالظبط اللي بيحصل بين الخيوط. القفل هو السكينة أو اللوح. الخيط اللي ماسك قفل ومستني قفل تاني ماسكه خيط بيستنى قفلك انت — ده انتظار دائري (circular wait)، وهو قلب الجمود.
الشرح العلمي: شروط Coffman الأربعة
الجمود مش بيحصل صدفة. في 1971 حدّد Coffman وزملاؤه أربع شروط لازم تتحقق كلها مع بعض عشان يحصل جمود. لو كسرت أي واحدة منهم، الجمود مستحيل:
- الإقصاء المتبادل (Mutual Exclusion): المورد مينفعش يتمسك بأكتر من خيط في نفس الوقت. القفل بطبيعته كده.
- المسك والانتظار (Hold and Wait): الخيط ماسك مورد وفي نفس الوقت مستني مورد تاني.
- عدم النزع (No Preemption): مفيش حد يقدر ياخد القفل من الخيط بالعافية، لازم يسيبه بنفسه.
- الانتظار الدائري (Circular Wait): سلسلة خيوط كل واحد مستني اللي بعده، وآخر واحد مستني الأول.
الحل العملي بيهاجم الشرط الرابع، لأنه أسهل واحد تكسره من غير ما تخرّب أداء برنامجك.
نعمله بأيدينا: كود يوريك الجمود
الكود ده بيعمل خيطين، كل واحد بياخد قفلين بترتيب معكوس. حطّينا sleep صغير عشان نضمن إن الجمود يحصل في كل مرة تشغّل:
import threading, time
lock_a = threading.Lock()
lock_b = threading.Lock()
def worker_1():
with lock_a:
time.sleep(0.1)
with lock_b:
pass
def worker_2():
with lock_b:
time.sleep(0.1)
with lock_a:
pass
t1 = threading.Thread(target=worker_1)
t2 = threading.Thread(target=worker_2)
t1.start(); t2.start()
t1.join(timeout=3)
t2.join(timeout=3)
if t1.is_alive() or t2.is_alive():
print("deadlock happened")
else:
print("done")
لو شغّلته، هيطبع رسالة الجمود. من غير الـ في كان البرنامج هيقف للأبد. لاحظ: مفيش استثناء، مفيش خطأ. البرنامج شكله شغّال، بس واقف.