Mutex (qarşılıqlı eksklüzivlik) — sinxronizasiya primitividir ki, yalnız bir thread-in kritik kod bölməsini hər an icra edə bilməsini təmin edir. Microsoft Docs (Synchronization Objects, 2024)-a görə, Mutex-in əsas prinsipi sahiblikdir: Mutex-i ələ keçirən thread onun sahibi olur və onu yalnız kritik bölmədən çıxdıqda azad edir. Mutex — Race Condition-un qarşısını almaq və çoxthreadli tətbiqlərdə məlumat bütövlüyünü təmin etmək üçün fundamental vasitədir.
Əsas məqamlar
Mutex (Mutual Exclusion — qarşılıqlı eksklüzivlik sözünün qısaltması) — çoxthreadli mühitdə ortaq resursa girişi idarə edən sinxronizasiya obyektidir. Thread kritik bölməyə daxil olduqda Mutex-i ələ keçirir. Başqa bir thread eyni Mutex-i ələ keçirməyə çalışarsa, birinci thread kilidi azad edənə qədər gözləmə vəziyyətinə keçir.
Mutex-in arxitekturası 1965-ci ildə Edsger Dijkstra tərəfindən hazırlanmış THE əməliyyat sisteminə gedib çıxır. Məhz Dijkstra semafor konsepsiyasını təqdim etdi, onlardan sonra Mutex xüsusi hal kimi ayrıldı — sahiblik dəstəyi ilə ikili semafor. Müasir ƏS-lər (Linux, Windows, Android) Mutex-i nüvə səviyyəsində tətbiq edir ki, bu da müxtəlif proseslər arasında sinxronizasiyanın düzgün işləməsini təmin edir.
Mutex-in əsas xüsusiyyəti ownership (sahiblik)-dir. Yalnız muteksi ələ keçirən thread onu azad edə bilər. Bu, Mutex-i ikili semaforadan fərqləndirir, burada istənilən thread siqnal (V-əməliyyatı) icra edə bilər. Sahiblik kilidin başqa bir thread tərəfindən təsadüfən azad edilməsinin qarşısını alır ki, bu da Mutex-i mobil inkişafda tipik sinxronizasiya ssenariləri üçün daha təhlükəsiz edir. Android Developer Docs (Processes and Threads, 2024)-ə görə, yüksək rəqabətdə Mutex-in synchronized əvəzinə istifadəsi məhsuldarlığı 30% artıra bilər.
Mutex iki vəziyyətdən birində olur: kidlənmiş (locked) — thread tərəfindən ələ keçirilmiş; sərbəst (unlocked) — ələ keçirilməmiş. İki əsas əməliyyat — lock() (ələ keçirmə) və unlock() (azad etmə). Əgər Mutex artıq ələ keçirilibsə, lock() çağıran thread azad olana qədər bloklanır. JVM-də bloklanmış thread BLOCKED vəziyyətinə keçir və CPU sərf etmir.
Mutex azad edildikdə, sistem gözləyən thread-lərdən hansının kilidi alacağını seçir. Qeyri-ədalətli (non-fair) planlaşdırmada seçim muteksi yenicə azad etmiş thread-ə düşə bilər — bu ötürmə qabiliyyətini artırır, lakin Starvation (aclıq) yarada bilər. Ədalətli (fair) planlaşdırıcı FIFO növbəsindən istifadə edir: ilk gözləyən thread kilidi birinci alır. ReentrantLock(true) məhz bu mexanizmi tətbiq edir.
Java/Kotlin-də Mutex tətbiqlərinin əksəriyyəti rekursiv (reentrant) ələ keçirməni dəstəkləyir. Əgər thread artıq Mutex-ə sahibdirsə və yenidən lock() çağırırsa, əməliyyat uğurlu olur — Mutex özünü bloklamır. Rekursiya sayğacı artır və thread unlock()-u lock() qədər çağırmalıdır. Bu, rekursiv çağırışlar və iç-içə kritik bölmələr üçün vacibdir.
Tipik bir tapşırığı nəzərdən keçirək — ortaq sayğacın ReentrantLock (Java/Kotlin-də klassik Mutex) ilə Race Condition-dan qorunması. Mutex olmadan kod səhv nəticə verərdi; Mutex ilə bütün 1000 thread sayğacın dəyərini garantili şəkildə artırır.
import java.util.concurrent.locks.ReentrantLock
class MutexCounter {
private val mutex = ReentrantLock()
private var count = 0
fun increment() {
mutex.lock()
try {
count++ // kritik bölmə
} finally {
mutex.unlock() // məcburi 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()) // Həmişə 1000
}
Finally blokuna diqqət yetirin — Mutex ilə işləyərkən məcburi nümunədir. Əgər kritik bölmə daxilində istisna yaranarsa, unlock() çağırılmayacaq və Mutex əbədi olaraq bloklanmış qalacaq — bu Deadlock-a gətirib çıxarır. Finally bloku bölmənin icrasının istənilən nəticəsində Mutex-in azad edilməsini təmin edir.
Kotlin-də alternativ yanaşma — withLock genişləndirmə funksiyasından istifadə, avtomatik olaraq lock/unlock-u finally ilə idarə edir.
fun increment() {
mutex.withLock { // lock + try/finally avtomatik
count++
}
}
fun getCount(): Int = mutex.withLock { count }
Bu üç sinxronizasiya mexanizmi tez-tez qarışdırılır, baxmayaraq ki, müxtəlif xüsusiyyətlərə və tətbiq sahələrinə malikdirlər. Mutex — ikili, sahibliklə. Semaphore — icazə sayğacı, sahibliksiz. Monitor — yüksək səviyyəli mexanizm, Mutex-i şərt dəyişənləri (condition variables) ilə birləşdirir. Fərqləri başa düşmək konkret tapşırıq üçün düzgün alətin seçilməsi üçün kritik əhəmiyyət daşıyır.
| Parametr | Mutex | Semaphore | Monitor |
|---|---|---|---|
| Tip | İkili (0/1) | Sayğaclı (0..N) | İkili + şərtlər |
| Sahiblik | Yalnız sahib unlock edə bilər | İstənilən thread signal edə bilər | Yalnız sahib |
| Rekursivlik | Adətən bəli (reentrant) | Xeyr | Bəli |
| Şərti gözləmə | Xeyr (Condition lazımdır) | Xeyr | Daxili (wait/notify) |
| Java/Kotlin nümunəsi | ReentrantLock | Semaphore(permits) | synchronized |
Nə vaxt Mutex seçməli: bir resursu eyni vaxtda girişdən qorumaq lazımdır — məsələn, ortaq kolleksiya, fayl və ya sayğac. Nə vaxt Semaphore seçməli — resurs hovuzuna eyni vaxtda girişlərin sayını məhdudlaşdırmaq lazımdır, məsələn 5 bağlantı üçün verilənlər bazası hovuzu. Nə vaxt Monitor seçməli — şərti gözləmə ilə sinxronizasiya lazımdır, məsələn wait/notify ilə istehsalçı-istehlakçı növbəsi. Müasir Android inkişafında synchronized tez-tez ReentrantLock və ya kotlinx.coroutines Mutex ilə əvəz olunur.
Ən geniş yayılmış səhv — unlock() çağırışı üçün finally blokunun olmaması. Əgər kritik bölmədə istisna yaranarsa, Mutex bloklanmış qalır və digər thread-lər sonsuz gözləyir. İstisnaların mümkünsüz olduğuna əmin olsanız belə — həmişə try/finally və ya withLock istifadə edin. Bu, xüsusilə mobil inkişafda vacib olan defensive programming prinsipidir, burada istisnalar yaddaş çatışmazlığı və ya Configuration Changes səbəbindən yarana bilər.
Tətbiqdə bir neçə Mutex istifadə edildikdə, onların ələ keçirilməsinin vahid ardıcıllığını təyin etmək kritikdir. Əgər Thread A M1 → M2, Thread B isə M2 → M1 ələ keçirirsə, Deadlock yaranır. Böyük layihələrdə (50 min sətirdən çox) kilidlərin ardıcıllığı memarlıq qərarında sənədləşdirilir və linterlərlə yoxlanılır. IntelliJ IDEA-da Lock Checker aləti kilidlərin ardıcıl olmayan ələ keçirilməsini avtomatik aşkarlayır.
Mutex-in 1-2 millisaniyədən artıq saxlanılması — səhv dizaynın əlamətidir. Kritik bölmə yalnız minimal zəruri əməliyyatları ehtiva etməlidir. Şəbəkə sorğuları, fayl giriş-çıxışı və mürəkkəb hesablamalar bloklanmış bölmədən kənarda icra edilməlidir. Android-də UI thread-də kilidin uzun müddət saxlanılması kadrların buraxılmasına (jank) və ANR-a gətirib çıxarır. Kritik bölmə əsasən oxuma əməliyyatlarından ibarətdirsə, ReadWriteLock istifadə edin.
kotlinx.coroutines kitabxanası klassik ReentrantLock-dan əsaslı şəkildə fərqlənən öz Mutex tətbiqini təqdim edir. Əsas fərq — suspending Mutex ƏS thread-ni bloklamır, korutini kilid azad olana qədər dayandırır. Bu o deməkdir ki, cari korutin Mutex-i gözləyərkən thread digər korutinləri icra edə bilər.
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 — thread-i bloklamır
count++
}
}
suspend fun getCount(): Int = mutex.withLock { count }
}
kotlinx Mutex-in əsas xüsusiyyətləri: qeyri-rekursivlik (non-reentrant) — ReentrantLock-dan fərqli olaraq, korutin sahib olduğu Mutex-i yenidən ələ keçirə bilməz. Bu lazımdırsa, Mutex əvəzinə Semaphore(1) istifadə edin. Bundan əlavə, kotlinx.coroutines-dən Mutex bloklamır: suspend vasitəsilə dayandırmadan istifadə edir ki, bu da hovuz thread-ni bloklamamağa imkan verir.
Praktikada suspending Mutex korutin kodunda klassik ReentrantLock-dan iki səbəbə görə üstündür: miqyaslama — bir korutin Mutex-i gözləyir, thread isə digər korutinləri xidmətləndirir, bu da sistemin ötürmə qabiliyyətini artırır; BlockedThread-in olmaması — bloklanmış thread-in yığınının saxlanmasına resurs sərf edilmir. JetBrains (Kotlin Coroutines Guide, 2024)-ə görə, suspending Mutex istifadəsi 100+ korutində ötürmə qabiliyyətini 40% artırır.
Tez-tez verilən suallar
Sahiblik (ownership) — əsas fərqdir. Mutex hansı thread-in onu ələ keçirdiyini xatırlayır və yalnız bu thread onu azad edə bilər. İkili semafor (Semaphore(1))-un sahibi yoxdur — istənilən thread release() icra edə bilər. Buna görə Mutex daha təhlükəsizdir: başqa bir thread təsadüfən başqasının kilidini azad edə bilməz, semafor isə edə bilər.
synchronized daha sadə və qısadır — onu vaxt məhdudiyyəti və ədalət nəzarəti olmayan sadə kritik bölmələr üçün istifadə edin. ReentrantLock-u vaxt məhdudiyyəti ilə TryLock, fair-planlaşdırma, Condition Variables və ya gözləyən thread-in kəsilməsi (lockInterruptibly) lazım olduqda istifadə edin. Korutinlər üçün həmişə kotlinx.coroutines.sync.Mutex istifadə edin.
Spinlock — thread-in yuxulamadığı, dövrədə (spin) kilidin vəziyyətini yoxladığı blokadadır. Spinlock CPU sərf edir, lakin kontekst keçidi etmir, bu da onu qısa kritik bölmələr (10 təlimata qədər) üçün faydalı edir. Mutex thread-i BLOCKED vəziyyətinə keçirir, bu da kontekst keçidi səbəbindən 10-50 mikrosaniyə baha başa gəlir, lakin CPU sərf etmir.
Linux nüvəsi səviyyəsində Mutex futex (fast userspace mutex) vasitəsilə tətbiq olunur. Thread əvvəlcə kilidi userspace-də atomik CAS (Compare-And-Swap) təlimatı ilə ələ keçirməyə çalışır. Mutex sərbəstdirsə — syscall olmadan ələ keçirmə baş verir. Məşğuldursa — thread futex(FUTEX_WAIT) syscall-ını edir və yuxulayır. Azad edildikdə futex(FUTEX_WAKE) syscall-ı bir gözləyən thread-i oyadır.
Bəli, proseslərarası Mutex (inter-process mutex) mövcuddur. Windows-da bu Named Mutex, Linux-da — PTHREAD_PROCESS_SHARED atributu ilə pthread_mutexattr_setpshared. Android Bionic libc da fayl deskriptorları vasitəsilə proseslərarası Mutex-i dəstəkləyir. Proseslərarası Mutex müxtəlif tətbiqlər və ya proses ilə onun uşaq prosesləri arasında sinxronizasiya üçün istifadə olunur.
Nəticə
Açar təslim mobil tətbiq hazırlayacağıq
IT Sectr 2017-ci ildən startaplar və bizneslər üçün iOS və Android tətbiqləri yaradır. Sizə məsləhət verəcəyik və ən yaxşı həlli təklif edəcəyik.
Həm də oxuyun