Race Condition — çoxiplikli proqramlaşdırmada son nəticənin iplərin hansı ardıcıllıqla yerinə yetirilməsindən asılı olduğu vəziyyətdir. Oracle Java Tutorials (2024) sənədləşməsinə görə, yarış vəziyyəti sinxronizasiya olmadan ümumi resursa eyni vaxtda giriş zamanı yaranır. Düzgün mexanizmlər olmadan Race Condition məlumatların zədələnməsinə və mobil tətbiqlərdə təkrar olunmayan xətalarına gətirib çıxarır.
Əsas məqamlar
Race Condition (yarış vəziyyəti) — çoxiplikli proqramda xəta, işin düzgünlüyü iplərin yerinə yetirilməsinin gözlənilməz ardıcıllığından asılı olduqda. İki və ya daha çox ip sinxronizasiya olmadan eyni vaxtda ümumi resursa müraciət etdikdə, resursun son vəziyyəti qeyri-müəyyən olur.
Mobil inkişafda Race Condition xüsusilə təhlükəlidir, çünki iplər prosessorun müxtəlif nüvələrində müxtəlif sürətlə yerinə yetirilə bilər. Tərtibatçı hansı ipin əməliyyatı birinci bitirəcəyini idarə edə bilməz — bunu əməliyyat sisteminin planlayıcısı həll edir. IBM-in tədqiqatına görə (Concurrency Bugs in Android, 2022), Android tətbiqlərindəki kritik xətaların təxminən 23%-i yarış vəziyyəti ilə bağlıdır.
Race Condition-un əsas xüsusiyyəti onun qeyri-deterministik olmasıdır. Eyni kod minlərlə dəfə xətasız işləyə bilər, sonra isə qəflətən çökə bilər. Bu diaqnostikanı xüsusilə çətin edir: xəta yalnız müəyyən şəraitdə — CPU yükü, aktiv iplərin sayı və planlaşdırma mərhələsində özünü göstərir.
Race Condition ip qeyri-atom əməliyyat yerinə yetirdikdə yaranır — bir neçə addımdan ibarət ardıcıllıq, başqa bir ip tərəfindən kəsilə bilər. Məsələn, counter++ inkrementasiya əməliyyatı əslində üç addımdan ibarətdir: yaddaşdan dəyəri oxumaq, bir vahid artırmaq və geri yazmaq. İki ip bu addımları qarışıq şəkildə yerinə yetirərsə, nəticə səhv olacaq.
Yarış vəziyyətinin əsas səbəbi — paylaşılan məlumatlara giriş zamanı sinxronizasiyanın olmaması. Bir ip obyekti dəyişdirdikdə, digəri isə eyni anda onu oxuduqda, oxuma nəticəsi gözlənilməz olur. Android-də bu problem tətbiq komponentlərinin (Activity, Service, BroadcastReceiver) müxtəlif iplərdə yerinə yetirilə bilməsi ilə daha da kəskinləşir.
Müasir Kotlin ilə Android inkişafında Race Condition tez-tez korutinlərin düzgün istifadə edilməməsi ilə yaranır. İki korutin sinxronizasiya olmadan müxtəlif Dispatchers-lərdə ümumi vəziyyətlə işləyirsə, nəticə gözlənilməz olacaq. Bu xüsusilə Dispatchers.IO və Dispatchers.Main-in ümumi mutable obyektlərlə birləşdirilməsində tez-tez baş verir.
Klassik məlumat yarışı nümunəsinə baxaq — bir neçə ipdən sayğacın inkrementasiyası. Sinxronizasiya olmadan son dəyər gözləniləndən az olacaq, çünki əməliyyatlar bir-birinin üzərinə qoyulur.
class RaceCounter {
private var counter = 0
fun increment() {
// Qeyri-atom əməliyyat — üç addım
counter++ // oxuyur, artırır, yazır
}
fun getCount(): Int = counter
}
fun main() = runBlocking {
val rc = RaceCounter()
val jobs = List(1000) {
launch(Dispatchers.Default) {
rc.increment()
}
}
jobs.forEach { it.join() }
println(rc.getCount()) // 1000 gözləyirik, ~997 alırıq
}
Bu nümunədə 1000 korutin eyni vaxtda increment() çağırır. counter++ əməliyyatının qeyri-atom olması səbəbindən son dəyər demək olar ki, heç vaxt 1000-ə bərabər olmur. Hər işə salma fərqli nəticə verir — Race Condition-un klassik simptomu. Yarışda nə qədər çox ip iştirak edərsə, gözlənilən dəyərdən kənarlaşma bir o qədər çox olar.
Düzəliş — atom tipi və ya bloklanmanın istifadəsi. Kotlin-də bu tapşırıq üçün java.util.concurrent.atomic paketindən AtomicInteger uyğundur. O, oxuma-dəyişdirmə-yazma əməliyyatlarının prosessor səviyyəsində vahid bölünməz hərəkət kimi yerinə yetirilməsinə zəmanət verir.
import java.util.concurrent.atomic.AtomicInteger
class SafeCounter {
private val counter = AtomicInteger(0)
fun increment() {
counter.incrementAndGet() // atom əməliyyatı
}
fun getCount(): Int = counter.get()
}
Məlumat yarışı — Race Condition-un ən geniş yayılmış növü. Bir ip dəyişənə məlumat yazdıqda, digəri isə eyni anda sinxronizasiya olmadan həmin dəyişəni oxuyanda və ya yazanda yaranır. Java Memory Model-də belə davranış qeyri-müəyyən sayılır — ip CPU səviyyəsində keşləmə səbəbindən aktual olmayan dəyər görə bilər.
Check-Then-Act nümunəsi — ip şərti yoxlayır, sonra bu yoxlamaya əsasən hərəkət edir. Yoxlama və hərəkət arasında başqa bir ip vəziyyəti dəyişə bilər. Tipik nümunə: kolleksiyada elementin mövcudluğunu yoxlamaq və sonra onu silmək. Android-də bu tez-tez SharedPreferences və ya verilənlər bazası ilə işləyərkən baş verir.
Read-Modify-Write — ipin dəyəri oxuduğu, onu lokal yaddaşda dəyişdirdiyi və geri yazdığı vəziyyət. Oxuma və yazma arasında başqa bir ip orijinal dəyəri dəyişibsə, dəyişdirmə nəticəsi itiriləcək. Klassik nümunə — yuxarıda Kotlin kodunda göstərilən counter++ əməliyyatı.
Software Transactional Memory (STM) — paylaşılan məlumatlar üzərində əməliyyatların verilənlər bazalarına bənzər şəkildə transaksiyalarda yerinə yetirildiyi yanaşma. İki transaksiya ziddiyyət təşkil edərsə, biri geri qaytarılır və təkrarlanır. Kotlin JVM üçün Multiverse STM kitabxanası mövcuddur, o, açıq bloklanmalar olmadan giriş ziddiyyətlərini avtomatik idarə edir. STM Android-də bir-biri ilə əlaqəli bir neçə obyektlə işləyərkən xüsusilə faydalıdır.
Race Condition-un xüsusi kateqoriyası — Activity-nin həyat dövrü ilə bağlı incə yarışlar (thin races). Tipik ssenari: fon ipi məlumat yükləməyi bitirir, lakin Activity artıq məhv edilib (ekranın döndərilməsi). Korutin mövcud olmayan View-i yeniləməyə çalışır və IllegalStateException ilə çökür. Həll yolu — Lifecycle Owner məhv edildikdə korutinləri avtomatik ləğv edən viewModelScope və Lifecycle-aware komponentlərindən istifadə etmək.
Race Condition-un aşkarlanması çoxiplikli tətbiqlərin debug edilməsində ən çətin vəzifələrdən biridir. Standart testlər nadir hallarda yarış vəziyyətini üzə çıxarır, çünki o, yalnız zamanlamanın spesifik uyğunlaşması ilə özünü göstərir. Google-a görə (Android Testing Guide, 2023), Race Condition-ların təxminən 70%-i test mühitində deterministik icra ardıcıllığı səbəbindən vahid testlərlə aşkar edilmir.
Əsas aşkarlama üsulları ixtisaslaşmış alətləri əhatə edir. ThreadSanitizer (TSan) — Android NDK-ya quraşdırılmış dinamik analizator, yaddaşa bütün müraciətləri izləyir və sinxronizasiya olunmayan girişi aşkar edir. Java/Kotlin kodu üçün Google fon iplərindən UI ipinə qeyri-qanuni müraciətləri tutan StrictMode ilə birlikdə Android Studio Layout Inspector-dən istifadə etməyi tövsiyə edir.
Digər effektiv yanaşma — yük altında testlərin çoxsaylı icrası ilə Stress Testing. JetBrains-dən Lincheck çərçivəsi JVM-də rəqabətli məlumat strukturlarını test etmək üçün xüsusi olaraq hazırlanmışdır. O, müxtəlif əməliyyat permutasiyaları ilə ssenariləri avtomatik yaradır və hər bir halda nəticələrin düzgünlüyünü yoxlayır.
| Alət | Platforma | Analiz növü |
|---|---|---|
| ThreadSanitizer | Android NDK | Dinamik yaddaş analizi |
| Intel Inspector | Windows | Statik + dinamik |
| Lincheck | JVM / Kotlin | Stress testi |
| StrictMode | Android | İcra zamanı tutma |
Atom dəyişənləri (AtomicInteger, AtomicLong, AtomicReference) — tək əməliyyatlar üçün məlumat yarışını aradan qaldırmağın ən asan yolu. Onlar bloklanma olmadan atom şəkildə yerinə yetirilən aşağı səviyyəli CPU CAS (Compare-And-Swap) təlimatlarından istifadə edir. Bu, aşağı rəqabət ssenarilərində maksimum performans verir.
Mutex və bloklanmalar — mürəkkəb əməliyyatlar və kritik bölmələr üçün uyğun olan klassik sinxronizasiya mexanizmi. Kotlin korutinləri üçün ipi bloklamaq əvəzinə asmağı (suspending) dəstəkləyən kotlinx.coroutines kitabxanasından suspending Mutex istifadə olunur. Bu, ənənəvi bloklanmalara xas olan boş gözləmədən qaçmağa imkan verir.
Vəziyyətin izolyasiyası — hər bir ipin öz məlumat nüsxəsi ilə işlədiyi memarlıq yanaşması. Mobil inkişafda buna hər bir aktyorun öz vəziyyətinə sahib olduğu və digər aktyorlarla mesaj mübadiləsi apardığı Aktyor modeli ilə nail olunur. Kotlin Coroutines Channel və SendChannel vasitəsilə Aktyorun tətbiqini təmin edir ki, bu da Race Condition-u memarlıq səviyyəsində tamamilə aradan qaldırır.
Əlavə qorunma səviyyəsi — Immutability: paylaşılan məlumatlar prinsipcə dəyişməzdirsə, Race Condition hətta sinxronizasiya olmadan da qeyri-mümkün olur. Kotlin-də bunun üçün val sahələri olan data class və iplər arasında nəşr edildikdə strukturun dəyişməzliyinə zəmanət verən kotlinx.collections.immutable kolleksiyaları istifadə olunur.
Tez-tez verilən suallar
Data Race — iki ipin eyni vaxtda eyni yaddaşa müraciət etdiyi və onlardan ən azı birinin yazma əməliyyatı yerinə yetirdiyi konkret Race Condition növüdür. Race Condition daha geniş anlayışdır, iplərin yerinə yetirilmə ardıcıllığından asılı olan istənilən xətaları, o cümlədən məntiqi yarış vəziyyətlərini əhatə edir.
Tamamilə aradan qaldırmaq mümkün deyil, ancaq minimuma endirmək olar. Dəyişməz obyektlərdən (immutable), atom tiplərindən və tək iplikli dispetçerli korutinlərdən istifadə edin. ThreadSafety qaydası ilə Android Lint kimi statik analiz alətləri potensial yarışları kompilyasiya mərhələsində aşkar etməyə kömək edir.
UI tətbiqlərində Race Condition tez-tez ekranın titrəməsi, məlumatların səhv göstərilməsi və ya siyahının yenilənməsi zamanı çökmə kimi özünü göstərir. Tipik ssenari: fon ipi məlumatları yükləyir və adapteri yeniləyir, istifadəçi isə bu anda siyahını sürüşdürür — Adapter DataSet-ə eyni vaxtda giriş yaranır.
volatile dəyişikliklərin iplər arasında görünməsinə zəmanət verir — volatile dəyişənə yazma dərhal bütün iplərə görünür. Bununla belə, volatile Read-Modify-Write və Check-Then-Act problemini həll etmir, çünki mürəkkəb əməliyyatların atomluğunu təmin etmir. Belə ssenarilər üçün bloklanmalar və ya atom sinifləri lazımdır.
Kotlin Coroutines-də Race Condition korutin planlayıcısı səviyyəsində yaranır, ƏS ip planlayıcısı səviyyəsində deyil. Korutinlər asma (suspend) nöqtələrində keçid edə bilər ki, bu da yarış üçün əlavə imkanlar yaradır. kotlinx.coroutines.debug aləti və IntelliJ IDEA debugeri korutinlərin vəziyyətini izləməyə kömək edir.
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