Hər mobil tətbiq eyni anda çoxlu tapşırıqları yerinə yetirir: şəbəkədən məlumat yükləyir, istifadəçi toxunuşlarını emal edir, interfeysi animasiya edir və faylları saxlayır. Əgər bütün bu kod bir şaxədə işləyirsə, tətbiq hər hansı şəbəkə gecikməsində donur. Çoxşaxəli (multithreading) və eynivaxtlılıq tətbiqin cavabdeh və səmərəli qalmasını təmin edən əsas anlayışlardır. Bu məqalədə bütün əsas alətləri nəzərdən keçirəcəyik: Main Thread və RunLoop-dan Kotlin korutinlərinə və iOS-da Combine-a qədər. Material Apple-ın rəsmi GCD sənədlərinə əsaslanır.
Əsas Məqamlar
Çoxşaxəli tətbiqin eyni anda bir neçə kod parçasını yerinə yetirmə qabiliyyətidir. Hər parça ayrı bir şaxədə (Thread) icra olunur — öz çağırış yığını olan yüngül proses. Mobil inkişafda şaxələr iki kateqoriyaya bölünür: Main Thread (UI şaxəsi) və Background Threads (fon şaxələri).
Əməliyyat sistemi özü şaxələrin prosessor nüvələri arasında paylanmasını idarə edir. Müasir cihazlarda 6–8 nüvə var, buna görə paralel icra işi sürətləndirə bilər. Bununla belə, şaxə yaratmaq bahalı əməliyyatdır, buna görə birbaşa Thread ilə işləmək tövsiyə edilmir. Bunun əvəzinə daha yüksək səviyyəli abstraksiyalardan istifadə olunur: DispatchQueue, OperationQueue, CoroutineDispatcher.
Eynivaxtlılıq (Concurrency) çoxşaxəlidən daha geniş anlayışdır. Eynivaxtlılıq o deməkdir ki, tapşırıqlar kontekst dəyişməsi vasitəsilə hətta bir nüvədə də "eyni anda" icra oluna bilər. Asinxronluq (Async/Await) — tapşırığın şaxəni bloklamadığı, nəticə gözlənilərkən nəzarəti qaytardığı proqramlaşdırma modelidir. Müasir dillər (Kotlin, Swift, Dart) daxili Async/Await dəstəyinə malikdir.
IT Sectr-də layihənin başlanğıcında düzgün çoxşaxəli arxitekturasına xüsusi diqqət yetiririk. Erkən mərhələdə buraxılan səhvlər tutulması çətin olan səhvlərə gətirib çıxarır: məlumat yarışları, qarşılıqlı bloklamalar və yük altında tətbiqin qeyri-sabitliyi. Hər layihəmiz planlaşdırma mərhələsində eynivaxtlılıq arxitektura icmalından keçir.
Main Thread (əsas şaxə) — mobil tətbiqdə UI-ya çıxışı olan yeganə şaxədir. Android-də buna UI Thread deyilir, iOS-da — Main Thread. Bütün interfeys əməliyyatları — mətn dəyişdirmə, animasiya, toxunuş emalı — yalnız Main Thread-də yerinə yetirilir. Əsas şaxədə ağır əməliyyat (fayl yükləmə, JSON təhlili) icra olunarsa, interfeys cavab verməyi dayandırır. Android-də bu ANR (Application Not Responding)-ə, iOS-da — ekranın "donmasına" səbəb olur.
Background Threads (fon şaxələri) UI ilə əlaqəli olmayan hər şey üçün nəzərdə tutulub: şəbəkə sorğuları, verilənlər bazası əməliyyatları, təsvir emalı, kriptoqrafiya. Tamamlandıqdan sonra nəticə göstərilmək üçün Main Thread-ə ötürülür. Hər platforma şaxələr arasında keçid etmək üçün öz alətlərini təqdim edir: iOS-da DispatchQueue.main.async, Android-də runOnUiThread və ya withContext(Dispatchers.Main).
RunLoop — iOS əsas şaxəsində hadisə emal dövrəsidir. RunLoop hadisələri (toxunuşlar, taymerlər, bildirişlər) gözləyir və onları müvafiq işləyicilərə çatdırır. Android-də ekvivalent hər Main Thread ilə əlaqəli Looper-dir. Main Looper növbədən sonsuz olaraq mesajları çıxarır və emal üçün Handler-ə ötürür. RunLoop və Looper-i başa düşmək yaddaş sızmalarından və interfeys "tutulmalarından" qaçmağa kömək edir.
Grand Central Dispatch (GCD) — C dili səviyyəsində çoxşaxəlini idarə etmək üçün Apple kitabxanasıdır. GCD DispatchQueue — tapşırıq növbələri ilə işləyir. Tərtibatçı əl ilə şaxə yaratmır; GCD mövcud prosessor nüvələri arasında tapşırıqları paylayaraq şaxə hovuzunu (Thread Pool) idarə edir. DispatchQueue iki növdür: Serial Queue (ardıcıl növbə — tapşırıqlar bir-birinin ardınca icra olunur) və Concurrent Queue (paralel növbə — tapşırıqlar eyni anda icra oluna bilər).
Main DispatchQueue — əsas şaxəyə bağlı ardıcıl növbədir. Global Queues — müxtəlif prioritetlərə (QoS — Quality of Service) malik paralel növbələrdir: userInteractive, userInitiated, utility, background. Düzgün QoS seçimi performans üçün kritikdir: .userInteractive — UI-ya təsir edən tapşırıqlar üçün (animasiyalar, renderinq); .background — zamana həssas olmayan tapşırıqlar üçün (sinxronizasiya, keş təmizləmə).
OperationQueue — əlavə imkanlarla GCD üzərində abstraksiyadır: tapşırıqları ləğv etmə, əməliyyatlar arasında asılılıqlar qurma, paralel əməliyyatların maksimum sayını idarə etmə. Əməliyyatlar Operation sinfinin (və ya BlockOperation) obyektləridir. Nümunə: şəkli yükləmək, sonra filtr tətbiq etmək və yalnız bundan sonra göstərmək lazımdırsa — asılılıqları olan OperationQueue bunu mükəmməl idarə edir. GCD-də bu addımları DispatchGroup və ya semafor vasitəsilə əl ilə sinxronizasiya etməli olardınız.
Swift 5.5+-da Async/Await — GCD-yə müasir alternativdir. async və await açar sözləri asinxron kodu xətti və oxunaqlı edir. Funksiyalar async olaraq işarələnir və çağırışlar await vasitəsilə gözlənilir. Sistem özü kontekst dəyişməsini idarə edir: standart olaraq async funksiyası fon şaxəsində icra olunur, UI yeniləməsi isə MainActor-da icra olunur. @MainActor — kodun əsas şaxədə icrasını təmin edən atributdur.
Coroutines (korutinlər) — JetBrains tərəfindən hazırlanmış Kotlin üçün yüngül şaxələrdir. Adi şaxələrdən fərqli olaraq, korutinlər müəyyən bir Thread-ə bağlı deyil. Minlərlə korutin əhəmiyyətli yük olmadan bir neçə şaxədə işləyə bilər. CoroutineScope korutinlərin həyat dövrünü idarə edir: viewModelScope ViewModel-ə bağlıdır, lifecycleScope — Activity/Fragment-ə. Çərçivə məhv edildikdə, bütün alt korutinlər avtomatik olaraq ləğv edilir.
Dispatchers korutinin hansı şaxə hovuzunda işləyəcəyini müəyyən edir: Dispatchers.Main — UI şaxəsi; Dispatchers.IO — şəbəkə sorğuları və disk əməliyyatları üçün; Dispatchers.Default — CPU gərgin hesablamalar üçün. Dispatcher dəyişdirmək üçün withContext istifadə olunur. Korutinlər strukturlaşdırılmış eynivaxtlılığı (structured concurrency) dəstəkləyir: hər korutinin valideyni var və valideyn ləğv edildikdə bütün alt korutinlər ləğv edilir. Bu yaddaş sızmalarının və asılı qalan tapşırıqların qarşısını alır.
Flow — korutin kitabxanasından soyuq asinxron məlumat axınıdır. Flow dəyərləri ardıcıl olaraq yayır: (1) prodüser məlumat yaradır, (2) operatorlar axını çevirir, (3) kollektor nəticəni istehlak edir. LiveData-dan fərqli olaraq, Flow mürəkkəb operator zəncirlərini (map, filter, flatMapConcat, catch) dəstəkləyir və tamamilə şaxə təhlükəsizdir. StateFlow və SharedFlow — Flow-un istiqamətli növləridir, UI vəziyyəti və birdəfəlik hadisələr (Snackbar, naviqasiya) üçün idealdır.
Channel — korutinlər arasında məlumat ötürmək üçün başqa bir korutin abstraksiyasıdır. Channel növbə kimi işləyir: bir göndərən (send) və bir və ya daha çox qəbul edən (receive). Buferləşdirilmiş kanallar (Channel(UNLIMITED), Channel(BUFFERED)) daşma zamanı davranışı konfiqurasiya etməyə imkan verir. Channel tez-tez geri çağırış əsaslı API-ləri korutinlərə bağlamaq üçün Flow ilə birlikdə istifadə olunur: callbackFlow { … }.
IT Sectr-də bütün Android layihələrində korutinlərdən və Flow-dan aktiv istifadə edirik. Bu sinxron görünən asinxron kod yazmağa, asanlıqla test etməyə (runTest, TestDispatcher) və əl ilə şaxə idarəçiliyi tələb etmir. Məlumat yükləməsi olan sadə bir korutin nümunəsi:
class UserRepository(
private val api: UserApi,
private val dao: UserDao
) {
suspend fun getUsers(): List<User> = withContext(Dispatchers.IO) {
return@withContext try {
val users = api.fetchUsers()
dao.insertAll(users)
users
} catch (e: Exception) {
dao.getAll()
}
}
}
Reaktiv proqramlaşdırma — məlumatların asinxron axınlar (Observable, Publisher) kimi yayıldığı paradigmadır. RxJava/RxKotlin — Android üçün ən populyar tətbiqdir, .NET Rx-dən port edilmişdir. RxSwift — iOS üçün oxşar kitabxanadır. Əsas komponentlər: Observable (hadisə mənbəyi), Observer (abunəçi), Scheduler (şaxə idarəçiliyi), Operators (axın çevrilməsi).
Combine — iOS 13-də təqdim edilmiş Apple-ın reaktiv proqramlaşdırma freymvorkudur. Combine Publisher (nəşriyyatçı) və Subscriber (abunəçi) protokollarından istifadə edir. RxSwift-dən fərqli olaraq, Combine SDK-ya daxildir və SwiftUI ilə sıx inteqrasiya edilmişdir. Combine-də operatorlar: map, filter, combineLatest, zip, debounce, throttle — məlumat bağlamasından UI-a və axtarış sorğusu debaunsinqinə qədər əksər ssenariləri əhatə edir.
Future və Promise — tək asinxron nəticə ilə işləmək üçün nümunələrdir. Future daha sonra əlçatan olacaq dəyəri təmsil edir. Promise dəyər təqdim etmək vədidir. Rx-də bu Single (bir uğurlu cavab və ya xəta), Combine-da — Future Publisher-dir. Təcrübədə Future/Promise tək API sorğuları üçün əlverişlidir, Observable/Publisher isə davamlı axınlar (geolokasiya, mətn daxiletmə) üçün əlverişlidir.
Callback və Delegate — asinxron əməliyyatlar üçün klassik nümunələrdir. Callback — arqument kimi ötürülən və əməliyyat tamamlandıqda çağırılan funksiyadır. Delegate — hadisə işləyici metodları olan protokolu tətbiq edən obyektdir. Çatışmazlıq: "callback cəhənnəmi" (iç-içə callback-lər) və xəta idarəetmə mürəkkəbliyi. NotificationCenter (iOS) və EventBus (Android) — boş bağlı kommunikasiya üçün faydalı, lakin gizli asılılıqlara səbəb olan yayım hadisə mexanizmləridir.
Çoxşaxəli yüksək performansın qapısını açır, eyni zamanda tutulması çətin səhvlər riski yaradır. Ən çox yayılanlar: Race Condition (yarış vəziyyəti), Deadlock (qarşılıqlı bloklama), Livelock (aktiv bloklama) və Starvation (şaxə aclığı). Bu problemləri başa düşmək hər hansı mobil tərtibatçı üçün məcburi bacarıqdır.
Race Condition iki və ya daha çox şaxə sinxronizasiya olmadan eyni məlumatları eyni anda oxuyub yazdıqda yaranır. Nəticə hansı şaxənin əvvəl icra olunmasından asılıdır. Klassik nümunə: iki şaxə sayğacı artırır. "Oxu → artır → yaz" əməliyyatı atomik deyil, buna görə eyni anda icra edildikdə bir artım "itir". Həll yolu — atomik əməliyyatlar (AtomicInteger, AtomicReference) və ya kilidlərdən (Mutex, Semaphore, synchronized) istifadə etməkdir.
Deadlock — hər şaxənin bir resursu saxladığı və başqa şaxə tərəfindən tutulmuş resursu gözlədiyi vəziyyətdir. Heç bir şaxə davam edə bilməz. Yaranma şərtləri: qarşılıqlı istisna, saxlama və gözləmə, öncədən alma yox, dövri gözləmə. Qarşısını alma: vahid kilid alma sırası təyin etmək, vaxt aşımı ilə tryLock istifadə etmək, Lock-Free alqoritmləri (ConcurrentHashMap, CopyOnWriteArrayList) tətbiq etmək.
Livelock — şaxələr bloklanmayıb, lakin faydalı iş görmədən davamlı olaraq bir-birinə resursları "ötürür". Nümunə: iki nəfər dəhlizdə qarşılaşır və hər ikisi eyni istiqamətdə hərəkət edərək yol verir. Starvation — bir şaxə digər şaxələr onu daim ələ keçirdiyi üçün resursa çıxış əldə edə bilmir. Həll: ədalətli kilidlər (fair locks), ehtiyatla şaxə prioritetləri.
Çoxşaxəli problemlərin qarşısını almaq üçün sinxronizasiya primitivlərindən istifadə olunur: Mutex (qarşılıqlı istisna), Semaphore (eyni vaxtda giriş sayını məhdudlaşdırma), Lock (tryLock ilə interfeys), Synchronized (JVM səviyyəli kilid), @MainActor (Swift — əsas şaxədə icraya zəmanət). Android-də Executors.newFixedThreadPool, newCachedThreadPool vasitəsilə ThreadPool da mövcuddur. Bununla belə, əl ilə hovuz idarəçiliyi köhnə layihələrin səlahiyyətidir; yeni layihələrdə korutinlərdən istifadə etmək daha yaxşıdır.
| Alət | Platforma | Növ | Xüsusiyyətlər |
|---|---|---|---|
| DispatchQueue (GCD) | iOS | Tapşırıq növbəsi | Ardıcıl/Paralel, QoS prioritetləri, Thread Pool sistem tərəfindən idarə olunur |
| OperationQueue | iOS | Əməliyyat növbəsi | Asılılıqlar, ləğv, maxConcurrentOperationCount |
| Coroutines + Flow | Android | Korutinlər | Yüngül, strukturlaşdırılmış eynivaxtlılıq, StateFlow, Channel |
| RxJava / RxKotlin | Android | Reaktiv axın | Observable, Schedulers, zəngin operator dəsti |
| Combine | iOS | Reaktiv axın | Publisher/Subscriber, SwiftUI inteqrasiyası |
| Async/Await + Task | iOS / Android | Asinxron model | Xətti kod, @MainActor, strukturlaşdırılmış eynivaxtlılıq |
Tez-tez verilən suallar
Main Thread (UI şaxəsi) interfeysin göstərilməsinə və toxunuşların emalına cavabdehdir. Background Thread fon tapşırıqlarını yerinə yetirir — məlumat yükləmə, hesablamalar, şəbəkə işi. Main Thread-i bloklamaq interfeysin donmasına səbəb olur (Android-də ANR, iOS-da frozen UI).
Race Condition — iki şaxə eyni anda paylaşılan məlumatlara daxil olduqda və nəticə icra sırasından asılı olduqda yaranan yarış vəziyyətidir. Sinxronizasiya vasitəsilə qarşısı alınır: Mutex, Semaphore, Lock, Synchronized, @MainActor və ya atomik əməliyyatlar.
Coroutines Android üçün müasir standartdır (JetBrains, Google tərəfindən dəstəklənir). RxJava/RxKotlin zəngin operator dəsti ilə reaktiv bir yanaşmadır. Coroutines asinxron çağırışlar üçün daha sadədir, RxJava mürəkkəb məlumat axınları üçün daha güclüdür. IT Sectr-də yeni layihələr üçün Coroutines + Flow istifadə edirik.
Deadlock — iki şaxənin bir-birinin resurslarını gözlədiyi qarşılıqlı bloklamadır. Livelock — şaxələr bloklanmayıb, lakin faydalı iş görmədən davamlı resursları ötürür. Hər iki problem düzgün kilid sırası və vaxt aşımları ilə həll olunur.
DispatchQueue şaxə idarəçiliyi üçün Grand Central Dispatch (GCD) abstraksiyasıdır. Main Queue tapşırıqları əsas şaxədə icra edir, Global Queues — fon şaxələrində. Serial Queue ardıcıl icranı təmin edir, Concurrent Queue — paralel. Müasir layihələrdə GCD tez-tez Async/Await və Task ilə əvəz olunur.
Xülasə
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.