Deadlock (باہمی بلاکنگ) ایک ایسی حالت ہے جس میں دو یا زیادہ تھریڈ دوسرے شرکاء کے پاس موجود وسائل کی رہائی کے لیے لامحدود انتظار کرتے ہیں۔ Oracle Java Tutorials (2024) کے مطابق، Deadlock چکریی انتظار میں پیدا ہوتا ہے جب ہر تھریڈ ایک لاک رکھتا ہے جو دوسرے تھریڈ کو درکار ہے۔ خصوصی پتہ لگانے والے ٹولز کے بغیر، Deadlock بغیر کسی ظاہری غلطی کے ایپلیکیشن کے عمل کو مکمل طور پر روک دیتا ہے۔
اہم نکات
Deadlock ملٹی تھریڈڈ پروگرامنگ میں ایک ایسی صورت حال ہے جہاں دو یا زیادہ تھریڈ ایک دوسرے کو مستقل طور پر بلاک کر دیتے ہیں۔ ہر تھریڈ دوسرے تھریڈ کے لیے ضروری وسائل رکھتا ہے اور گمشدہ وسائل کے حصول کے انتظار میں اسے جاری نہیں کرتا۔ نتیجتاً، کوئی بھی تھریڈ عمل جاری نہیں رکھ سکتا۔
موبائل ڈویلپمنٹ میں Deadlock خاص طور پر اہم ہے کیونکہ یہ استثناء یا کریش پیدا نہیں کرتا۔ ایپلیکیشن صارف کے اعمال کا جواب دینا بند کر دیتی ہے (ANR — Application Not Responding)، اور واحد راستہ عمل کو زبردستی ختم کرنا ہے۔ Google (Android Performance Patterns، 2023) کے مطابق، Google Play Console میں تقریباً 15% ANR رپورٹس پس منظر کے تھریڈ میں باہمی بلاکنگ سے متعلق ہیں۔
Deadlock اور دیگر ہم وقتی مسائل کے درمیان بنیادی فرق بیرونی مداخلت کے بغیر اس کی ناقابل واپسی ہے۔ تھریڈ خود بخود وسائل جاری نہیں کریں گے کیونکہ آپریٹنگ سسٹم شیڈیولر زبردستی لاک واپس نہیں لے سکتا۔ یہ Deadlock کو Livelock سے ممتاز کرتا ہے، جہاں تھریڈ فعال ہیں لیکن مفید کام نہیں کر رہے۔
1971 میں، Edward G. Coffman نے Deadlock کے وقوع کے لیے ضروری چار لازمی شرائط وضع کیے۔ اگر ان میں سے کم از کم ایک موجود نہ ہو، تو باہمی بلاکنگ ناممکن ہے۔ یہ شرائط Coffman کے شرائط کے نام سے جانے جاتے ہیں اور تمام Deadlock روک تھام الگورتھم کی بنیاد بناتے ہیں۔
ایک وسیلہ کسی بھی وقت صرف ایک تھریڈ کے ذریعے حاصل کیا جا سکتا ہے۔ اگر کوئی وسیلہ متعدد تھریڈ کے ذریعے بیک وقت پڑھنے کی اجازت دیتا ہے (مثلاً ReadWriteLock پڑھنے کے موڈ میں)، تو Deadlock نہیں ہوتا۔ یہ شرط Mutex اور لاک کی نوعیت سے پیدا ہوتی ہے۔
ایک تھریڈ پہلے سے حاصل کردہ وسائل کو رکھتا ہے اور ساتھ ہی دوسرے وسائل کے حصول کا انتظار کرتا ہے۔ اگر کوئی تھریڈ اگلے وسائل کی درخواست کرنے سے پہلے موجودہ وسائل کو جاری کر سکتا ہے (دو مرحلہ لاکنگ کے ذریعے)، تو Hold and Wait کی شرط ٹوٹ جاتی ہے۔ Android میں، یہ اکثر اس وقت ظاہر ہوتا ہے جب کوئی تھریڈ ڈیٹا بیس لاک رکھتا ہے اور SharedPreferences لاک حاصل کرنے کی کوشش کرتا ہے۔
آپریٹنگ سسٹم کسی تھریڈ سے زبردستی لاک نہیں لے سکتا۔ وسیلہ صرف اس وقت جاری ہوتا ہے جب تھریڈ خود اسے جاری کرتا ہے۔ کچھ نظاموں میں (مثلاً SQLite WAL موڈ)، انفرادی کارروائیوں کی سطح پر جبری بے دخلی نافذ کی جاتی ہے، جو Deadlock کے خطرے کو کم کرتی ہے۔
تھریڈ کی ایک بند زنجیر موجود ہوتی ہے، جن میں سے ہر ایک زنجیر میں اگلے تھریڈ کے پاس موجود وسائل کا انتظار کرتا ہے۔ مثال کے طور پر، تھریڈ A وسائل 1 رکھتا ہے اور وسائل 2 کا انتظار کرتا ہے، تھریڈ B وسائل 2 رکھتا ہے اور وسائل 1 کا انتظار کرتا ہے۔ یہ واحد شرط ہے جسے ڈویلپر ساختی طور پر ختم کر سکتا ہے — لاک درجہ بندی کے ذریعے۔ اگر تمام تھریڈ وسائل کو سختی سے متعین عالمی ترتیب میں حاصل کریں، تو چکر جسمانی طور پر ناممکن ہے۔
عملی طور پر، Android ایپلیکیشنز میں، Deadlock اکثر مختلف سطحوں کے لاک کے مخفی تقاطع کی وجہ سے ہوتا ہے: ڈیٹا بیس لاک (Room)، SharedPreferences لاک، اور میموری میں کلیکشن لاک۔ ان میں سے ہر لاک مختلف اجزاء کے ذریعے منظم کیا جاتا ہے، اور حصول کی ترتیب کے لیے مرکزی پروٹوکول کے بغیر، ڈویلپر غیر ارادی طور پر چکر بنا دیتے ہیں۔
باہمی بلاکنگ کی ایک کلاسک مثال دیکھتے ہیں — دو تھریڈ مختلف ترتیب میں لاک حاصل کرتے ہیں۔ اگر پہلا تھریڈ وسائل A کو لاک کرتا ہے اور B حاصل کرنے کی کوشش کرتا ہے، اور دوسرا B کو لاک کرتا ہے اور A حاصل کرنے کی کوشش کرتا ہے، تو Deadlock ہوتا ہے۔
class DeadlockExample {
private val lockA = Any()
private val lockB = Any()
fun operationA() {
synchronized(lockA) {
Thread.sleep(50) // کام کا تخیل
synchronized(lockB) {
println("operationA مکمل")
}
}
}
fun operationB() {
synchronized(lockB) { // الٹی ترتیب
Thread.sleep(50)
synchronized(lockA) {
println("operationB مکمل")
}
}
}
}
fun main() {
val ex = DeadlockExample()
Thread { ex.operationA() }.start()
Thread { ex.operationB() }.start()
// ایپلیکیشن ہمیشہ کے لیے پھنس جائے گی — Deadlock!
}
اس مثال میں، operationA lockA حاصل کرتا ہے، اور operationB lockB حاصل کرتا ہے۔ پھر ہر ایک دوسرا لاک حاصل کرنے کی کوشش کرتا ہے — اور دونوں لامحدود انتظار کرتے ہیں۔ پروگرام بغیر کسی غلطی کے پیغام کے پھنس جاتا ہے۔ اسے ٹھیک کرنے کا واحد طریقہ تمام طریقوں میں لاک حصول کی یکساں ترتیب کو یقینی بنانا ہے۔
ان تینوں ہم وقتی مسائل کو اکثر الجھایا جاتا ہے، لیکن ان کے میکانزم اور نتائج بنیادی طور پر مختلف ہیں۔ Deadlock — مکمل رکاوٹ، Starvation — وسائل کا لامحدود انتظار، Livelock — فعال بے عملی۔ فرق کو سمجھنا صحیح حل کی حکمت عملی منتخب کرنے کے لیے انتہائی اہم ہے۔
| خصوصیت | Deadlock | Starvation | Livelock |
|---|---|---|---|
| تھریڈ کی حالت | مسدود (BLOCKED) | تیار (RUNNABLE) | فعال (RUNNABLE) |
| کام کی انجام دہی | نہیں | نہیں | ہاں، لیکن بے کار |
| وجہ | چکریی انتظار | غیر منصفانہ نظام الاوقات | غلط تنازعہ ہینڈلنگ |
| پتہ لگانا | Thread Dump، ٹائم آؤٹ | پیش رفت مانیٹرنگ | دوبارہ کوشش کاؤنٹر |
Starvation (بھوک) اس وقت ہوتی ہے جب شیڈیولر دوسروں کے حق میں کم ترجیحی تھریڈ کی عمل داری کو مسلسل ملتوی کرتا ہے۔ Deadlock کے برعکس، تھریڈ مسدود نہیں ہے — یہ عمل کے لیے تیار ہے لیکن CPU وقت حاصل نہیں کرتا۔ Android میں، ایک عام منظر نامہ کم ترجیحی پس منظر کا تھریڈ ہے جو کبھی عمل نہیں کرتا اگر UI تھریڈ اور Service تھریڈ مسلسل فعال ہوں۔
Livelock (فعال لاک) ایک ایسی صورت حال ہے جہاں تھریڈ مسدود نہیں ہیں لیکن مفید کام کیے بغیر ایک دوسرے کے اعمال پر لامتناہی ردعمل دیتے ہیں۔ کلاسک مثال — دو لوگ راہداری میں ملتے ہیں اور دونوں راستہ دینے کی کوشش کرتے ہیں، ایک ہی سمت میں حرکت کرتے ہیں۔ Deadlock کے برعکس، Livelock میں تھریڈ CPU استعمال کرتے ہیں، ڈیوائس کی بیٹری ختم کرتے ہیں۔
Thread Dump JVM اور Android Runtime میں باہمی بلاکنگ کا پتہ لگانے کا بنیادی ٹول ہے۔ ڈمپ کے دوران، JVM خود بخود مانیٹر کے درمیان انحصار گراف کا تجزیہ کرتا ہے اور Deadlock چکر کو نشان زد کرتا ہے۔ Android Studio میں، تھریڈ ڈمپ Android Profiler یا ADB Shell سے kill -3 PID کمانڈ کے ذریعے حاصل کیا جا سکتا ہے۔
رن ٹائم پر خودکار Deadlock کا پتہ لگانا Watchdog ٹائمر کے ذریعے لاگو کیا جاتا ہے۔ اگر کوئی تھریڈ مخصوص ٹائم آؤٹ کے اندر کوئی کارروائی مکمل نہیں کرتا، تو watchdog ڈمپ شروع کرتا ہے اور Crash Reporting سسٹم (Firebase Crashlytics, Sentry) کو رپورٹ بھیجتا ہے۔ Sentry (Issue Resolution Report، 2024) کے مطابق، watchdog ترتیب دینے سے Deadlock کی تشخیص کا وقت ہفتوں سے چند گھنٹوں تک کم ہو جاتا ہے۔
ترقی کے مرحلے میں، JetBrains کا ThreadSafe جامد تجزیہ کار اور Lock Checker ماڈیول کے ساتھ Checker Framework مؤثر ہیں۔ یہ ٹولز ماخذ کوڈ کی سطح پر لاک حصول کی ترتیب کا تجزیہ کرتے ہیں اور ممکنہ چکر کے بارے میں خبردار کرتے ہیں۔ مزید برآں، Test-Driven Deadlock Detection کی سفارش کی جاتی ہے — تناؤ کے ٹیسٹ جو سینکڑوں تھریڈ میں مختلف لاک ترتیبوں کے ساتھ کارروائیاں چلاتے ہیں۔
خصوصی توجہ کے مستحق ہے Cooperative Deadlock Detection — ایک طریقہ جہاں تھریڈ ایک عالمی رجسٹری کے ذریعے حاصل کردہ لاک کے بارے میں معلومات کا تبادلہ کرتے ہیں۔ اگر کوئی تھریڈ ممکنہ چکر کا پتہ لگاتا ہے، تو یہ تمام وسائل جاری کرتا ہے اور کارروائی دوبارہ کرتا ہے۔ یہ نقطہ نظر تقسیم شدہ نظاموں (Apache ZooKeeper، Google Chubby) میں استعمال ہوتا ہے اور Jetpack Sync جیسی لائبریریوں کے ذریعے آہستہ آہستہ موبائل ڈویلپمنٹ میں اپنایا جا رہا ہے۔
سب سے قابل اعتماد طریقہ پوری ایپلیکیشن میں لاک حصول کی عالمی ترتیب قائم کرنا ہے۔ اگر تمام تھریڈ ہمیشہ پہلے چھوٹے نمبر والا لاک حاصل کریں، پھر بڑے نمبر والا، تو چکریی انتظار (Circular Wait شرط) ناممکن ہے۔ بڑے منصوبوں میں، ترتیب کو دستاویزی شکل دی جاتی ہے اور کوڈ جائزہ کے ذریعے تصدیق کی جاتی ہے۔
TryLock ایک لاکنگ طریقہ ہے جو تھریڈ کو لامحدود طور پر بلاک نہیں کرتا بلکہ مخصوص وقت کے اندر لاک حاصل نہ ہونے پر false لوٹاتا ہے۔ Java میں، یہ ReentrantLock.tryLock(timeout, TimeUnit) کے ذریعے، Kotlin Coroutines میں — ٹائم آؤٹ کے ساتھ Mutex.withLock کے ذریعے لاگو کیا جاتا ہے۔ ناکامی پر، تھریڈ تمام حاصل کردہ وسائل جاری کرتا ہے اور بعد میں دوبارہ کوشش کرتا ہے۔
بینکر الگورتھم Edsger Dijkstra کی طرف سے تجویز کردہ Deadlock روک تھام کا ایک نظریاتی طریقہ ہے۔ یہ وسائل کی تقسیم کو بینک لین دین کے طور پر ماڈل کرتا ہے: نظام کوئی وسیلہ مختص نہیں کرتا اگر یہ غیر محفوظ حالت (deadlock) کا باعث بن سکتا ہے۔ عملی طور پر، تھریڈ کی زیادہ سے زیادہ ضروریات کو پہلے سے جاننے کی دشواری کی وجہ سے الگورتھم شاذ و نادر ہی موبائل ڈویلپمنٹ میں استعمال ہوتا ہے، لیکن اس کے اصول SQLite ڈیٹا بیس اور فائل سسٹم میں استعمال ہوتے ہیں۔
اکثر پوچھے گئے سوالات
نہیں، باہمی بلاکنگ کے لیے کم از کم دو تھریڈ ضروری ہیں۔ ایک تھریڈ والے کوڈ میں، تمام کارروائیاں ترتیب وار عمل میں آتی ہیں، لہٰذا چکریی انتظار ناممکن ہے۔ تاہم، فائل لاک یا بین العمل سیمفور استعمال کرتے وقت عمل کے درمیان Deadlock ہو سکتا ہے۔
Coroutines میں، Deadlock معطل افعال (suspend) کی سطح پر ہوتا ہے اور OS تھریڈ کو بلاک نہیں کرتا، جو اسے کم نمایاں بناتا ہے۔ kotlinx.coroutines کا Mutex ایک معطل کرنے والا لاک (suspending) ہے — یہ تھریڈ کو بلاک نہیں کرتا، لیکن coroutine عمل نہیں ہوتا۔ پتہ لگانے کے لیے، kotlinx-coroutines-debug ماڈیول سے DebugProbes استعمال کریں۔
SQLite Deadlock اس وقت ہوتا ہے جب ڈیٹا بیس کے دو کنکشن مختلف ترتیبوں میں لین دین انجام دینے کی کوشش کرتے ہیں۔ SQLite ایسی صورتحال کا پتہ لگاتا ہے اور SQLITE_BUSY یا SQLITE_LOCKED ایرر کوڈ لوٹاتا ہے۔ Android میں، ایک ہی ڈیٹا بیس انسٹینس اور @Transaction کے ذریعے لین دین کے ساتھ Room استعمال کرنے کی سفارش کی جاتی ہے، جو کنکشن کے درمیان Deadlock کو ختم کرتا ہے۔
Android Runtime میں ایک بلٹ ان Deadlock ڈیٹیکٹر ہے جو ANR (Application Not Responding) پیدا ہونے پر چلتا ہے۔ نظام ایپلیکیشن کے تمام تھریڈ کے Thread Dump کا تجزیہ کرتا ہے اور باہمی بلاکنگ کو نشان زد کرتا ہے۔ نتیجہ /data/anr/traces.txt اور Google Play Console کے ANR Reports سیکشن میں دستیاب ہے۔
سب سے پہلے، ایپلیکیشن کے تمام تھریڈ کا Thread Dump حاصل کریں۔ تجزیہ کریں کہ ہر تھریڈ کون سے لاک رکھتا ہے اور کون سے حاصل کرنے کی کوشش کر رہا ہے۔ وقت کی حد سے تجاوز کرنے پر خودکار ڈمپ کے ساتھ Watchdog ٹائمر لاگو کریں۔ اصلاح کے بعد، تکرار کو روکنے کے لیے CI پائپ لائن میں ThreadSafety lint اصول شامل کریں۔
خلاصہ
ہم ایک موبائل ایپلیکیشن ٹرنکی تیار کریں گے
IT Sectr 2017 سے اسٹارٹ اپس اور کاروبار کے لیے iOS اور Android ایپلیکیشنز بناتا ہے۔ ہم آپ کو مشورہ دیں گے اور بہترین حل تجویز کریں گے۔
مزید پڑھیں