Mutex (o'zaro eksklyuzivlik) — sinxronizatsiya primitividir, u faqat bitta thread har bir momentda kodning kritik qismini bajarishini kafolatlaydi. Microsoft Docs (Synchronization Objects, 2024) ga ko'ra, Mutex ning asosiy prinsipi egalikdir: Mutex ni egallagan thread uning egasiga aylanadi va uni faqat kritik qismdan chiqqanda bo'shatadi. Mutex — Race Condition ning oldini olish va ko'p threadli ilovalarda ma'lumotlar yaxlitligini ta'minlash uchun fundamental vositadir.
Asosiy ma'lumotlar
Mutex (Mutual Exclusion — o'zaro eksklyuzivlik so'zining qisqartmasi) — ko'p threadli muhitda umumiy resursga kirishni boshqaradigan sinxronizatsiya obyektidir. Thread kritik qismga kirganda, Mutex ni egallaydi. Agar boshqa thread xuddi shu Mutex ni egallashga harakat qilsa, u birinchi thread qulfni bo'shatguncha kutish holatiga o'tadi.
Mutex arxitekturasi 1965 yilda Edsger Dijkstra tomonidan ishlab chiqilgan THE operatsion tizimiga borib taqaladi. Aynan Dijkstra semaforlar kontseptsiyasini kiritdi, ulardan keyin Mutex alohida holat sifatida ajralib chiqdi — egalik qo'llab-quvvatlangan ikkilik semafor. Zamonaviy OT lar (Linux, Windows, Android) Mutex ni yadro darajasida amalga oshiradi, bu turli jarayonlar o'rtasida ham sinxronizatsiyaning to'g'ri ishlashini ta'minlaydi.
Mutex ning asosiy xususiyati ownership (egalik) dir. Faqat muteksni egallagan thread uni bo'shata oladi. Bu Mutex ni ikkilik semafordan farqlaydi, unda har qanday thread signal (V-operatsiya) bajarishi mumkin. Egalik qulfning boshqa thread tomonidan tasodifiy bo'shatilishining oldini oladi, bu Mutex ni mobil ishlanmada odatiy sinxronizatsiya stsenariylari uchun xavfsizroq qiladi. Android Developer Docs (Processes and Threads, 2024) ga ko'ra, yuqori raqobatda Mutex ni synchronized o'rniga ishlatish unumdorlikni 30% ga oshirishi mumkin.
Mutex ikki holatdan birida bo'ladi: qulflangan (locked) — thread tomonidan egallangan; bo'sh (unlocked) — egallanmagan. Ikki asosiy operatsiya — lock() (egallash) va unlock() (bo'shatish). Agar Mutex allaqachon egallangan bo'lsa, lock() ni chaqirgan thread bo'shatilgunga qadar bloklanadi. JVM da bloklangan thread BLOCKED holatiga o'tadi va CPU sarflamaydi.
Mutex bo'shatilganda, sistem kutayotgan threadlardan qaysi biri qulfni olishini tanlaydi. Adolatsiz (non-fair) rejalashtirishda tanlov muteksni hozirgina bo'shatgan threadga tushishi mumkin — bu o'tkazish qobiliyatini oshiradi, lekin Starvation (ochlik) ga olib kelishi mumkin. Adolatli (fair) rejalashtiruvchi FIFO navbatidan foydalanadi: birinchi kutayotgan thread qulfni birinchi bo'lib oladi. ReentrantLock(true) aynan shu mexanizmni amalga oshiradi.
Java/Kotlin da Mutex ni amalga oshirishlarning aksariyati rekursiv (reentrant) egallashni qo'llab-quvvatlaydi. Agar thread allaqachon Mutex ga ega bo'lsa va yana lock() ni chaqirsa, operatsiya muvaffaqiyatli bo'ladi — Mutex o'zini bloklamaydi. Rekursiya hisoblagichi ortadi va thread unlock() ni lock() chaqirilgancha marta chaqirishi kerak. Bu rekursiv chaqiruvlar va ichma-ich kritik qismlar uchun muhimdir.
Oddiy vazifani ko'rib chiqaylik — umumiy hisoblagichni ReentrantLock (Java/Kotlin da klassik Mutex) yordamida Race Condition dan himoya qilish. Mutex siz kod noto'g'ri natija berardi; Mutex bilan barcha 1000 thread hisoblagich qiymatini kafolatlangan holda oshiradi.
import java.util.concurrent.locks.ReentrantLock
class MutexCounter {
private val mutex = ReentrantLock()
private var count = 0
fun increment() {
mutex.lock()
try {
count++ // kritik qism
} finally {
mutex.unlock() // majburiy 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()) // Har doim 1000
}
Finally blokiga e'tibor bering — Mutex bilan ishlashda majburiy namunadir. Agar kritik qism ichida istisno yuz bersa, unlock() chaqirilmaydi va Mutex abadiy qulflangan qoladi — bu Deadlock ga olib keladi. Finally bloki qismning istalgan natijasida Mutex ning bo'shatilishini kafolatlaydi.
Kotlin da alternativ yondashuv — withLock kengaytma funksiyasidan foydalanish, u avtomatik ravishda lock/unlock ni finally bilan boshqaradi.
fun increment() {
mutex.withLock { // lock + try/finally avtomatik
count++
}
}
fun getCount(): Int = mutex.withLock { count }
Ushbu uch sinxronizatsiya mexanizmi ko'pincha aralashtiriladi, garchi ular turli xususiyatlarga va qo'llanish sohalariga ega. Mutex — ikkilik, egalik bilan. Semaphore — ruxsatnomalar hisoblagichi, egaliksiz. Monitor — yuqori darajadagi mexanizm, Mutex ni shart o'zgaruvchilari (condition variables) bilan birlashtiradi. Farqlarni tushunish muayyan vazifa uchun to'g'ri vositani tanlashda muhim ahamiyatga ega.
| Parametr | Mutex | Semaphore | Monitor |
|---|---|---|---|
| Tur | Ikkilik (0/1) | Hisoblagichli (0..N) | Ikkilik + shartlar |
| Egalik | Faqat egasi unlock qila oladi | Har qanday thread signal bera oladi | Faqat egasi |
| Rekursivlik | Odatda ha (reentrant) | Yo'q | Ha |
| Shartli kutish | Yo'q (Condition kerak) | Yo'q | Ichki (wait/notify) |
| Java/Kotlin misoli | ReentrantLock | Semaphore(permits) | synchronized |
Qachon Mutex tanlash: bitta resursni bir vaqtda kirishdan himoya qilish kerak — masalan, umumiy kolleksiya, fayl yoki hisoblagich. Qachon Semaphore tanlash — resurslar hovuziga bir vaqtda kirishlar sonini cheklash kerak, masalan 5 ta ulanish uchun ma'lumotlar bazasi hovuzi. Qachon Monitor tanlash — shartli kutish bilan sinxronizatsiya kerak, masalan wait/notify orqali ishlab chiqaruvchi-iste'molchi navbati. Zamonaviy Android ishlanmasida synchronized ko'pincha ReentrantLock yoki kotlinx.coroutines Mutex bilan almashtiriladi.
Eng keng tarqalgan xato — unlock() chaqiruvi uchun finally blokining yo'qligi. Agar kritik qism ichida istisno yuz bersa, Mutex qulflangan qoladi va boshqa threadlar abadiy kutadi. Istisnolar mumkin emasligiga ishonchingiz komil bo'lsa ham — har doim try/finally yoki withLock dan foydalaning. Bu mobil ishlanmada ayniqsa muhim bo'lgan defensive printing prinsipidir, bu erda istisnolar xotira yetishmasligi yoki Configuration Changes tufayli yuz berishi mumkin.
Ilovada bir nechta Mutex ishlatilganda, ularni egallashning yagona tartibini o'rnatish muhimdir. Agar Thread A M1 → M2 ni, Thread B esa M2 → M1 ni egallasa, Deadlock yuzaga keladi. Katta loyihalarda (50 ming qatordan ko'p) qulflar tartibi arxitektura qarorida hujjatlashtiriladi va linterlar bilan tekshiriladi. IntelliJ IDEA dagi Lock Checker vositasi qulflarning izchil bo'lmagan egallash tartibini avtomatik aniqlaydi.
Mutex ni 1-2 millisekunddan ortiq ushlab turish — noto'g'ri dizayn belgisidir. Kritik qism faqat minimal zarur operatsiyalarni o'z ichiga olishi kerak. Tarmoq so'rovlari, fayl kiritish-chiqarish va murakkab hisob-kitoblar bloklangan qismdan tashqarida bajarilishi kerak. Android da UI threadyadida qulfni uzoq ushlab turish kadrlarning tushib qolishiga (jank) va ANR ga olib keladi. Agar kritik qism asosan o'qish operatsiyalaridan iborat bo'lsa, ReadWriteLock dan foydalaning.
kotlinx.coroutines kutubxonasi klassik ReentrantLock dan tubdan farq qiladigan o'z Mutex amalga oshirishni taqdim etadi. Asosiy farq — suspending Mutex OT threadini bloklamaydi, balki korutinni qulf bo'shatilgunga qadar to'xtatadi. Bu shuni anglatadiki, joriy korutin Mutex ni kutayotganda thread boshqa korutinlarni bajarishi mumkin.
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 — threadni bloklamaydi
count++
}
}
suspend fun getCount(): Int = mutex.withLock { count }
}
kotlinx Mutex ning asosiy xususiyatlari: rekursiv emas (non-reentrant) — ReentrantLock dan farqli o'laroq, korutin o'zi egalik qilgan Mutex ni qayta egallay olmaydi. Agar bu kerak bo'lsa, Mutex o'rniga Semaphore(1) dan foydalaning. Bundan tashqari, kotlinx.coroutines dan Mutex bloklamaydi: suspend orqali to'xtatishdan foydalanadi, bu esa hovuz threadini bloklamaslikka imkon beradi.
Amalda, suspending Mutex korutin kodida klassik ReentrantLock dan ikki sababga ko'ra afzal: masshtablash — bir korutin Mutex ni kutadi, thread esa boshqa korutinlarga xizmat ko'rsatadi, bu tizimning o'tkazish qobiliyatini oshiradi; BlockedThread ning yo'qligi — bloklangan thread stekini saqlashga resurs sarflanmaydi. JetBrains (Kotlin Coroutines Guide, 2024) ga ko'ra, suspending Mutex dan foydalanish 100+ korutinda o'tkazish qobiliyatini 40% ga oshiradi.
Tez-tez beriladigan savollar
Egalik (ownership) — asosiy farq. Mutex qaysi thread uni egallaganini eslab qoladi va faqat shu thread uni bo'shata oladi. Ikkilik semafor (Semaphore(1)) ning egasi yo'q — har qanday thread release() ni bajarishi mumkin. Shuning uchun Mutex xavfsizroq: boshqa thread tasodifan birovning qulfini bo'shata olmaydi, semafor esa bo'shata oladi.
synchronized soddaroq va qisqaroq — uni vaqt cheklovi va adolat nazoratisiz oddiy kritik qismlar uchun ishlating. ReentrantLock ni vaqt cheklovi bilan TryLock, fair-rejalashtirish, Condition Variables yoki kutayotgan threadni to'xtatish (lockInterruptibly) kerak bo'lganda ishlating. Korutinlar uchun har doim kotlinx.coroutines.sync.Mutex dan foydalaning.
Spinlock — thread uxlamaydigan, balki sikl (spin) ichida qulf holatini tekshiradigan blokirovkadir. Spinlock CPU sarflaydi, lekin kontekstni almashtirmaydi, bu uni qisqa kritik qismlar (10 ta ko'rsatmaga qadar) uchun foydali qiladi. Mutex thread ni BLOCKED holatiga o'tkazadi, bu kontekst almashinuvi sababli 10-50 mikrosekundga qimmatroq, lekin CPU sarflamaydi.
Linux yadrosi darajasida Mutex futex (fast userspace mutex) orqali amalga oshirilgan. Thread avval qulfni userspace da atomik CAS (Compare-And-Swap) ko'rsatmasi orqali egallashga harakat qiladi. Mutex bo'sh bo'lsa — egallash syscall siz sodir bo'ladi. Band bo'lsa — thread futex(FUTEX_WAIT) syscall ni qiladi va uxlaydi. Bo'shatilganda futex(FUTEX_WAKE) syscall bir kutayotgan threadni uyg'otadi.
Ha, jarayonlararo Mutex (inter-process mutex) mavjud. Windows da bu Named Mutex, Linux da — PTHREAD_PROCESS_SHARED atributi bilan pthread_mutexattr_setpshared. Android Bionic libc ham fayl deskriptorlari orqali jarayonlararo Mutex ni qo'llab-quvvatlaydi. Jarayonlararo Mutex lar turli ilovalar yoki jarayon va uning bola jarayonlari o'rtasida sinxronizatsiya uchun ishlatiladi.
Xulosa
Biz kalit topshirig'i bilan mobil ilovani ishlab chiqamiz
IT Sectr 2017-yildan beri startaplar va korxonalar uchun iOS va Android ilovalarini yaratadi. Biz sizga maslahat beramiz va eng yaxshi yechimni taklif qilamiz.