Mutex (باہمی اخراج) ایک ہم آہنگی کا ابتدائیہ ہے جو اس بات کی ضمانت دیتا ہے کہ کسی بھی وقت صرف ایک تھریڈ کوڈ کے اہم حصے کو انجام دے سکتا ہے۔ Microsoft Docs (Synchronization Objects, 2024) کے مطابق، Mutex کا بنیادی اصول ملکیت ہے: جو تھریڈ Mutex کو حاصل کرتا ہے وہ اس کا مالک بن جاتا ہے اور اہم حصے سے باہر نکلتے وقت ہی اسے جاری کرتا ہے۔ Mutex ریس کنڈیشن (Race Condition) کو روکنے اور ملٹی تھریڈڈ ایپلیکیشنز میں ڈیٹا کی سالمیت کو یقینی بنانے کے لیے ایک بنیادی آلہ ہے۔
اہم نکات
Mutex (Mutual Exclusion — باہمی اخراج کا مخفف) ایک ہم آہنگی آبجیکٹ ہے جو ملٹی تھریڈڈ ماحول میں مشترکہ وسائل تک رسائی کا انتظام کرتا ہے۔ جب ایک تھریڈ اہم حصے میں داخل ہوتا ہے، تو یہ Mutex حاصل کرتا ہے۔ اگر کوئی دوسرا تھریڈ اسی Mutex کو حاصل کرنے کی کوشش کرتا ہے، تو اسے پہلے تھریڈ کے لاک جاری کرنے تک انتظار کی حالت میں ڈال دیا جاتا ہے۔
Mutex کا فن تعمیر THE آپریٹنگ سسٹم سے تعلق رکھتا ہے، جسے Edsger Dijkstra نے 1965 میں ڈیزائن کیا تھا۔ Dijkstra نے سیمافور کا تصور متعارف کرایا، جس سے بعد میں Mutex ایک خاص معاملے کے طور پر ابھرا — ملکیت کی حمایت کے ساتھ ایک بائنری سیمافور۔ جدید آپریٹنگ سسٹم (Linux، Windows، Android) کرنل کی سطح پر Mutex کو نافذ کرتے ہیں، جو مختلف عملوں کے درمیان بھی درست ہم آہنگی کو یقینی بناتا ہے۔
Mutex کی کلیدی خاصیت ملکیت (ownership) ہے۔ صرف وہ تھریڈ جس نے میوٹیکس حاصل کیا ہے اسے جاری کر سکتا ہے۔ یہ Mutex کو بائنری سیمافور سے ممتاز کرتا ہے، جہاں کوئی بھی تھریڈ سگنل (V-آپریشن) انجام دے سکتا ہے۔ ملکیت کسی دوسرے تھریڈ کے ذریعے لاک کے حادثاتی طور پر جاری ہونے سے روکتی ہے، جس سے Mutex موبائل ڈویلپمنٹ میں عام ہم آہنگی کے منظرناموں کے لیے زیادہ محفوظ ہو جاتا ہے۔ Android Developer Docs (Processes and Threads, 2024) کے مطابق، زیادہ مسابقت میں synchronized کے بجائے Mutex استعمال کرنے سے کارکردگی 30% بہتر ہو سکتی ہے۔
Mutex دو حالتوں میں سے ایک میں ہوتا ہے: مقفل (locked) — تھریڈ کے ذریعے حاصل کردہ؛ یا غیر مقفل (unlocked) — حاصل نہیں کیا گیا۔ دو بنیادی عملیے ہیں: lock() (حاصل کرنا) اور unlock() (جاری کرنا)۔ اگر Mutex پہلے سے مقفل ہے، تو lock() کال کرنے والا تھریڈ لاک جاری ہونے تک مسدود ہو جاتا ہے۔ JVM میں، مسدود تھریڈ BLOCKED حالت میں چلا جاتا ہے اور CPU استعمال نہیں کرتا۔
جب Mutex جاری ہوتا ہے، نظام منتخب کرتا ہے کہ کون سا منتظر تھریڈ لاک حاصل کرے گا۔ غیر منصفانہ (non-fair) نظامت میں، انتخاب اس تھریڈ پر پڑ سکتا ہے جس نے ابھی میوٹیکس جاری کیا ہے — اس سے تھروپٹ بڑھتا ہے لیکن بھوک (Starvation) کا باعث بن سکتا ہے۔ ایک منصفانہ (fair) نظامت کار FIFO قطار استعمال کرتا ہے: پہلا منتظر تھریڈ پہلے لاک حاصل کرتا ہے۔ ReentrantLock(true) بالکل اسی میکانزم کو نافذ کرتا ہے۔
Java/Kotlin میں زیادہ تر Mutex نفاذ ری اینٹرینٹ حصول کی حمایت کرتے ہیں۔ اگر ایک تھریڈ پہلے سے Mutex کا مالک ہے اور دوبارہ lock() کال کرتا ہے، تو عملیہ کامیاب ہوتا ہے — Mutex خود کو مسدود نہیں کرتا۔ تکرار کاؤنٹر بڑھ جاتا ہے، اور تھریڈ کو lock() کے برابر بار unlock() کال کرنا ہوگا۔ یہ تکرار پذیر کالز اور اندرونی اہم حصوں کے لیے اہم ہے۔
ایک عام کام پر غور کریں — ReentrantLock (Java/Kotlin میں کلاسک Mutex) استعمال کرتے ہوئے مشترکہ کاؤنٹر کو ریس کنڈیشن سے بچانا۔ Mutex کے بغیر، کوڈ غلط نتائج دے گا؛ Mutex کے ساتھ، تمام 1000 تھریڈ قابل اعتماد طور پر کاؤنٹر کی قدر بڑھاتے ہیں۔
import java.util.concurrent.locks.ReentrantLock
class MutexCounter {
private val mutex = ReentrantLock()
private var count = 0
fun increment() {
mutex.lock()
try {
count++ // اہم حصہ
} finally {
mutex.unlock() // لازمی finally
}
}
fun getCount(): Int {
mutex.lock()
try {
return count
} finally {
mutex.unlock()
}
}
}
fun main() = runBlocking {
val counter = MutexCounter()
val jobs = List(1000) {
launch(Dispatchers.Default) {
counter.increment()
}
}
jobs.forEach { it.join() }
println(counter.getCount()) // ہمیشہ 1000
}
finally بلاک پر توجہ دیں — Mutex کے ساتھ کام کرتے وقت ایک لازمی نمونہ۔ اگر اہم حصے کے اندر کوئی استثناء ہوتا ہے، تو unlock() کال نہیں ہوگا اور Mutex ہمیشہ کے لیے مقفل رہے گا — یہ ڈیڈلاک کا باعث بنتا ہے۔ finally بلاک اس بات کی ضمانت دیتا ہے کہ حصے کا نفاذ کسی بھی طرح ختم ہو، Mutex جاری ہو جائے گا۔
Kotlin میں ایک متبادل طریقہ withLock توسیعی فنکشن کا استعمال ہے، جو finally کے ساتھ خودکار طور پر lock/unlock کو سنبھالتا ہے۔
fun increment() {
mutex.withLock { // lock + try/finally خودکار
count++
}
}
fun getCount(): Int = mutex.withLock { count }
ان تینوں ہم آہنگی کے میکانزم میں اکثر الجھن ہوتی ہے، حالانکہ ان کی مختلف خصوصیات اور استعمال کے معاملات ہیں۔ Mutex ملکیت کے ساتھ بائنری ہے۔ سیمافور ملکیت کے بغیر ایک اجازت کاؤنٹر ہے۔ مانیٹر ایک اعلیٰ سطحی میکانزم ہے جو Mutex کو شرطی متغیرات کے ساتھ جوڑتا ہے۔ فرق کو سمجھنا کسی خاص کام کے لیے صحیح آلہ منتخب کرنے کے لیے انتہائی اہم ہے۔
| پیرامیٹر | Mutex | سیمافور | مانیٹر |
|---|---|---|---|
| قسم | بائنری (0/1) | گنتی (0..N) | بائنری + شرائط |
| ملکیت | صرف مالک unlock کر سکتا ہے | کوئی بھی تھریڈ signal کر سکتا ہے | صرف مالک |
| ری اینٹرینسی | عام طور پر ہاں (reentrant) | نہیں | ہاں |
| شرطی انتظار | نہیں (Condition چاہیے) | نہیں | بلٹ ان (wait/notify) |
| Java/Kotlin میں مثال | ReentrantLock | Semaphore(permits) | synchronized |
Mutex کب منتخب کریں: آپ کو ایک واحد وسائل کو بیک وقت رسائی سے بچانے کی ضرورت ہے — مثال کے طور پر، مشترکہ مجموعہ، فائل یا کاؤنٹر۔ سیمافور کب منتخب کریں — آپ کو وسائل کے پول تک بیک وقت رسائی کی تعداد محدود کرنے کی ضرورت ہے، جیسے 5 کنکشنز کے ساتھ ڈیٹابیس کنکشن پول۔ مانیٹر کب منتخب کریں — آپ کو شرطی انتظار کے ساتھ ہم آہنگی کی ضرورت ہے، جیسے wait/notify کے ذریعے پروڈیوسر-صارف قطار۔ جدید Android ڈویلپمنٹ میں، synchronized کو اکثر ReentrantLock یا kotlinx.coroutines Mutex سے بدل دیا جاتا ہے۔
سب سے عام غلطی unlock() کال کرنے کے لیے finally بلاک کی عدم موجودگی ہے۔ اگر اہم حصے میں کوئی استثناء ہوتا ہے، تو Mutex مقفل رہتا ہے اور دوسرے تھریڈ ہمیشہ انتظار کرتے ہیں۔ یہاں تک کہ اگر آپ کو یقین ہے کہ استثناء ناممکن ہے — ہمیشہ try/finally یا withLock استعمال کریں۔ یہ دفاعی پروگرامنگ کا اصول ہے، خاص طور پر موبائل ڈویلپمنٹ میں اہم جہاں میموری کی کمی یا Configuration Changes کی وجہ سے استثناء پیدا ہو سکتے ہیں۔
جب ایک ایپلیکیشن متعدد Mutex استعمال کرتی ہے، تو ایک مستقل حصول کی ترتیب قائم کرنا انتہائی اہم ہے۔ اگر تھریڈ A، M1 → M2 حاصل کرتا ہے، اور تھریڈ B، M2 → M1 حاصل کرتا ہے، تو ڈیڈلاک ہوتا ہے۔ بڑے منصوبوں (50 ہزار سے زیادہ کوڈ لائنز) میں، لاک کی ترتیب کو فن تعمیر کے فیصلے میں دستاویزی کیا جاتا ہے اور لنٹرز کے ذریعے تصدیق کی جاتی ہے۔ IntelliJ IDEA میں Lock Checker آلہ خود بخود غیر مستقل لاک حصول کی ترتیب کا پتہ لگاتا ہے۔
Mutex کو 1-2 ملی سیکنڈ سے زیادہ رکھنا خراب ڈیزائن کی علامت ہے۔ اہم حصے میں صرف کم سے کم ضروری عملیے شامل ہونے چاہئیں۔ نیٹ ورک کی درخواستیں، فائل I/O اور پیچیدہ حسابات مقفل بلاک سے باہر انجام دیے جانے چاہئیں۔ Android میں، UI تھریڈ میں طویل عرصے تک لاک رکھنے سے فریم ڈراپ (jank) اور ANR ہوتا ہے۔ اگر اہم حصہ بنیادی طور پر پڑھنے کے عملیات پر مشتمل ہے تو ReadWriteLock استعمال کریں۔
kotlinx.coroutines لائبریری اپنا خود کا Mutex نفاذ فراہم کرتی ہے، جو کلاسک ReentrantLock سے بنیادی طور پر مختلف ہے۔ بنیادی فرق یہ ہے کہ suspending Mutex OS تھریڈ کو مسدود نہیں کرتا بلکہ لاک جاری ہونے تک کوروٹین کو معطل کرتا ہے۔ اس کا مطلب ہے کہ تھریڈ دوسرے کوروٹین کو انجام دے سکتا ہے جب کہ موجودہ کوروٹین Mutex کا انتظار کر رہا ہے۔
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
class CoroutineCounter {
private val mutex = Mutex()
private var count = 0
suspend fun increment() {
mutex.withLock { // suspending — تھریڈ کو مسدود نہیں کرتا
count++
}
}
suspend fun getCount(): Int = mutex.withLock { count }
}
kotlinx Mutex کی اہم خصوصیات: غیر ری اینٹرینٹ (non-reentrant) — ReentrantLock کے برعکس، ایک کوروٹین اس Mutex کو دوبارہ حاصل نہیں کر سکتا جس کا وہ پہلے سے مالک ہے۔ اگر یہ ضروری ہو تو، Mutex کے بجائے Semaphore(1) استعمال کریں۔ مزید برآں، kotlinx.coroutines کا Mutex غیر مسدود ہے: یہ suspend کے ذریعے معطلی کا استعمال کرتا ہے، جس سے یہ پول تھریڈ کو مسدود نہیں کرتا۔
عملی طور پر، کوروٹین کوڈ میں suspending Mutex دو وجوہات کی بنا پر کلاسک ReentrantLock پر ترجیح رکھتا ہے: اسکیل ایبلٹی — ایک کوروٹین Mutex کا انتظار کرتا ہے جبکہ تھریڈ دوسرے کوروٹین کی خدمت کرتا ہے، جس سے سسٹم تھروپٹ بڑھتا ہے؛ کوئی BlockedThread نہیں — مسدود تھریڈ کے اسٹیک کو ذخیرہ کرنے پر وسائل خرچ نہیں ہوتے۔ JetBrains (Kotlin Coroutines Guide, 2024) کے مطابق، 100+ کوروٹین کے ساتھ suspending Mutex استعمال کرنے سے تھروپٹ 40% بہتر ہوتا ہے۔
اکثر پوچھے گئے سوالات
ملکیت (ownership) بنیادی فرق ہے۔ Mutex یاد رکھتا ہے کہ کس تھریڈ نے اسے حاصل کیا، اور صرف وہی تھریڈ اسے جاری کر سکتا ہے۔ بائنری سیمافور (Semaphore(1)) کا کوئی مالک نہیں ہے — کوئی بھی تھریڈ release() کال کر سکتا ہے۔ اس لیے Mutex زیادہ محفوظ ہے: کوئی دوسرا تھریڈ غلطی سے کسی اور کا لاک جاری نہیں کر سکتا، جبکہ سیمافور کر سکتا ہے۔
synchronized آسان اور چھوٹا ہے — اسے ٹائم آؤٹ اور انصاف کے کنٹرول کے بغیر سادہ اہم حصوں کے لیے استعمال کریں۔ ReentrantLock کو اس وقت استعمال کریں جب آپ کو ٹائم آؤٹ کے ساتھ TryLock، منصفانہ نظامت، شرطی متغیرات یا منتظر تھریڈ میں خلل ڈالنے (lockInterruptibly) کی ضرورت ہو۔ کوروٹین کے لیے، ہمیشہ kotlinx.coroutines.sync.Mutex استعمال کریں۔
اسپن لاک ایک لاک ہے جہاں تھریڈ سوتا نہیں بلکہ ایک لوپ میں گھومتا ہے (spin) لاک کی حالت چیک کرتا ہے۔ اسپن لاک CPU استعمال کرتا ہے لیکن سیاق و سباق تبدیل نہیں کرتا، جو اسے چھوٹے اہم حصوں (10 ہدایات تک) کے لیے فائدہ مند بناتا ہے۔ Mutex تھریڈ کو BLOCKED حالت میں ڈالتا ہے، جو سیاق و سباق کی تبدیلی کی وجہ سے 10-50 مائیکرو سیکنڈ زیادہ مہنگا ہے، لیکن CPU ضائع نہیں کرتا۔
Linux کرنل کی سطح پر، Mutex futex (fast userspace mutex) کے ذریعے نافذ کیا جاتا ہے۔ تھریڈ پہلے جوہری CAS (Compare-And-Swap) ہدایت کے ذریعے یوزر اسپیس میں لاک حاصل کرنے کی کوشش کرتا ہے۔ اگر Mutex آزاد ہے — حصول بغیر syscall کے ہوتا ہے۔ اگر مصروف ہے — تھریڈ syscall futex(FUTEX_WAIT) کرتا ہے اور سو جاتا ہے۔ جاری ہونے پر، syscall futex(FUTEX_WAKE) ایک منتظر تھریڈ کو جگاتا ہے۔
ہاں، بین العمل Mutex (inter-process mutex) موجود ہیں۔ Windows میں یہ Named Mutex ہے، Linux میں — PTHREAD_PROCESS_SHARED وصف کے ساتھ pthread_mutexattr_setpshared۔ Android کی Bionic libc بھی فائل ڈسکریپٹرز کے ذریعے بین العمل Mutex کی حمایت کرتی ہے۔ بین العمل Mutex مختلف ایپلیکیشنز کے درمیان یا کسی عمل اور اس کے ذیلی عملوں کے درمیان ہم آہنگی کے لیے استعمال ہوتے ہیں۔
خلاصہ
ہم ایک موبائل ایپلیکیشن ٹرنکی تیار کریں گے
IT Sectr 2017 سے اسٹارٹ اپس اور کاروبار کے لیے iOS اور Android ایپلیکیشنز بناتا ہے۔ ہم آپ کو مشورہ دیں گے اور بہترین حل تجویز کریں گے۔
مزید پڑھیں