Každá mobilní aplikace provádí mnoho úkolů současně: načítá data ze sítě, zpracovává dotyky uživatele, animuje rozhraní a ukládá soubory. Pokud celý tento kód běží v jednom vlákně, aplikace při jakémkoli zpoždění sítě zamrzne. Vícevláknovost (multithreading) a souběžnost jsou klíčové koncepty, které umožňují aplikaci zůstat responzivní a efektivní. V tomto článku pokryjeme všechny hlavní nástroje: od Main Thread a RunLoop po korutiny Kotlin a Combine na iOS. Materiál vychází z oficiální dokumentace Apple GCD.
Hlavní Body
Vícevláknovost je schopnost aplikace provádět několik fragmentů kódu současně. Každý fragment běží v samostatném vlákně (Thread) — lehkém procesu s vlastním zásobníkem volání. V mobilním vývoji se vlákna dělí do dvou kategorií: Main Thread (vlákno UI) a Background Threads (vlákna na pozadí).
Operační systém sám řídí distribuci vláken mezi jádry procesoru. Moderní zařízení mají 6–8 jader, takže paralelní provádění může práci urychlit. Vytváření vláken je však nákladná operace, proto se přímá práce s Thread nedoporučuje. Místo toho se používají abstrakce vyšší úrovně: DispatchQueue, OperationQueue, CoroutineDispatcher.
Souběžnost (Concurrency) je širší pojem než vícevláknovost. Souběžnost znamená, že úkoly mohou být prováděny „současně" i na jednom jádře prostřednictvím přepínání kontextu. Asynchronnost (Async/Await) je programovací model, kde úkol neblokuje vlákno, ale vrací řízení, zatímco čeká na výsledek. Moderní jazyky (Kotlin, Swift, Dart) mají vestavěnou podporu pro Async/Await.
Ve společnosti IT Sectr věnujeme zvláštní pozornost správné architektuře vícevláknovosti na začátku projektu. Chyby v rané fázi vedou k obtížně odhalitelným bugům: závodům dat, deadlockům a nestabilitě aplikace pod zátěží. Každý náš projekt prochází kontrolou architektury souběžnosti ve fázi plánování.
Main Thread (hlavní vlákno) — jediné vlákno v mobilní aplikaci, které má přístup k UI. Na Androidu se nazývá UI Thread, na iOS — Main Thread. Všechny operace rozhraní — změna textu, animace, zpracování dotyků — se provádějí pouze na Main Thread. Pokud se na hlavním vlákně provádí těžká operace (načítání souboru, parsování JSON), rozhraní přestane reagovat. Na Androidu to vede k ANR (Application Not Responding), na iOS — k „zamrznutí" obrazovky.
Background Threads (vlákna na pozadí) jsou určena pro vše, co nesouvisí s UI: síťové požadavky, databázové operace, zpracování obrazu, kryptografie. Po dokončení je výsledek předán na Main Thread k zobrazení. Každá platforma poskytuje vlastní nástroje pro přepínání mezi vlákny: DispatchQueue.main.async na iOS, runOnUiThread nebo withContext(Dispatchers.Main) na Androidu.
RunLoop — smyčka zpracování událostí na hlavním vlákně iOS. RunLoop čeká na události (dotyky, časovače, oznámení) a distribuuje je příslušným handlerům. Na Androidu je ekvivalentem Looper, spojený s každým Main Thread. Main Looper nekonečně vytahuje zprávy z fronty a předává je Handleru ke zpracování. Porozumění RunLoop a Looper pomáhá předcházet únikům paměti a "zadrhávání" rozhraní.
Grand Central Dispatch (GCD) — knihovna Apple pro správu vícevláknovosti na úrovni jazyka C. GCD pracuje s DispatchQueue — frontami úkolů. Vývojář nevytváří vlákna ručně; GCD spravuje fond vláken (Thread Pool) a rozděluje úkoly na dostupná jádra procesoru. DispatchQueue jsou dvojího typu: Serial Queue (sériová fronta — úkoly se provádějí jeden po druhém) a Concurrent Queue (souběžná fronta — úkoly mohou být prováděny současně).
Main DispatchQueue — sériová fronta připojená k hlavnímu vláknu. Global Queues — souběžné fronty s různými prioritami (QoS — Quality of Service): userInteractive, userInitiated, utility, background. Výběr správné QoS je kritický pro výkon: .userInteractive — pro úkoly ovlivňující UI (animace, vykreslování); .background — pro časově nekritické úkoly (synchronizace, čištění mezipaměti).
OperationQueue — abstrakce nad GCD s dalšími možnostmi: zrušení úkolů, nastavení závislostí mezi operacemi, kontrola maximálního počtu souběžných operací. Operace jsou objekty třídy Operation (nebo BlockOperation). Příklad: pokud potřebujete načíst obrázek, poté použít filtr a teprve poté jej zobrazit — OperationQueue se závislostmi to zvládne perfektně. V GCD byste museli tyto kroky ručně synchronizovat pomocí DispatchGroup nebo semaforu.
Async/Await v Swift 5.5+ — moderní alternativa k GCD. Klíčová slova async a await činí asynchronní kód lineárním a čitelným. Funkce jsou označeny jako async a volání jsou očekávána pomocí await. Systém sám řídí přepínání kontextu: ve výchozím nastavení je async funkce spuštěna na vlákně na pozadí, zatímco aktualizace UI běží na MainActor. @MainActor — atribut zaručující provedení kódu na hlavním vlákně.
Coroutines (korutiny) — lehká vlákna pro Kotlin vyvinutá společností JetBrains. Na rozdíl od běžných vláken nejsou korutiny vázány na konkrétní Thread. Tisíce korutin mohou běžet na několika vláknech bez významné režie. CoroutineScope spravuje životní cyklus korutin: viewModelScope je vázán na ViewModel, lifecycleScope — na Activity/Fragment. Když je rozsah zničen, všechny podřízené korutiny jsou automaticky zrušeny.
Dispatchers určují, na kterém fondu vláken se korutina spustí: Dispatchers.Main — vlákno UI; Dispatchers.IO — pro síťové požadavky a diskové operace; Dispatchers.Default — pro výpočty náročné na CPU. Pro přepnutí dispatcheru se používá withContext. Korutiny podporují strukturovanou souběžnost: každá korutina má rodiče a když je rodič zrušen, všechny podřízené korutiny jsou zrušeny. To zabraňuje únikům paměti a visícím úkolům.
Flow — studený asynchronní datový tok z knihovny korutin. Flow vysílá hodnoty sekvenčně: (1) producent generuje data, (2) operátory transformují tok, (3) kolektor spotřebovává výsledek. Na rozdíl od LiveData Flow podporuje složité řetězce operátorů (map, filter, flatMapConcat, catch) a je zcela bezpečný pro vlákna. StateFlow a SharedFlow — horké varianty Flow, ideální pro stav UI a jednorázové události (Snackbar, navigace).
Channel — další abstrakce korutin pro předávání dat mezi korutinami. Channel funguje jako fronta: jeden odesílatel (send) a jeden nebo více příjemců (receive). Kanály s vyrovnávací pamětí (Channel(UNLIMITED), Channel(BUFFERED)) umožňují konfigurovat chování při přetečení. Channel se často používá s Flow pro propojení API založených na callback s korutinami: callbackFlow { … }.
Ve společnosti IT Sectr aktivně používáme korutiny a Flow ve všech projektech Android. To umožňuje psát asynchronní kód, který vypadá synchronně, snadno se testuje (runTest, TestDispatcher) a nevyžaduje ruční správu vláken. Příklad jednoduché korutiny s načítáním dat:
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()
}
}
}
Reaktivní programování — paradigma, kde se data šíří jako asynchronní toky (Observable, Publisher). RxJava/RxKotlin — nejoblíbenější implementace pro Android, portovaná z .NET Rx. RxSwift — podobná knihovna pro iOS. Hlavní komponenty: Observable (zdroj událostí), Observer (odběratel), Scheduler (správa vláken), Operators (transformace toku).
Combine — framework Apple pro reaktivní programování představený v iOS 13. Combine používá protokoly Publisher (vydavatel) a Subscriber (odběratel). Na rozdíl od RxSwift je Combine integrován do SDK a úzce propojen se SwiftUI. Operátory v Combine: map, filter, combineLatest, zip, debounce, throttle — pokrývají většinu scénářů: od vázání dat k UI po debounce vyhledávacího dotazu.
Future a Promise — vzory pro práci s jedním asynchronním výsledkem. Future představuje hodnotu, která bude k dispozici později. Promise je slib poskytnout hodnotu. V Rx je to Single (jedna úspěšná odpověď nebo chyba), v Combine — Future Publisher. V praxi jsou Future/Promise vhodné pro jednotlivé API požadavky, zatímco Observable/Publisher — pro spojité toky (geolokace, vstup textu).
Callback a Delegate — klasické vzory pro asynchronní operace. Callback — funkce předaná jako argument a volaná při dokončení operace. Delegate — objekt implementující protokol s metodami obsluhy událostí. Nevýhoda: "callback hell" (vnořené callbacky) a složitost zpracování chyb. NotificationCenter (iOS) a EventBus (Android) — mechanismy vysílání událostí, užitečné pro volně propojenou komunikaci, ale vedoucí k implicitním závislostem.
Vícevláknovost otevírá dveře vysokému výkonu, ale zároveň vytváří riziko obtížně odhalitelných chyb. Nejčastější: Race Condition (závodní stav), Deadlock (uváznutí), Livelock (aktivní uváznutí) a Starvation (hladovění vlákna). Porozumění těmto problémům je povinnou dovedností každého mobilního vývojáře.
Race Condition nastává, když dvě nebo více vláken současně čtou a zapisují stejná data bez synchronizace. Výsledek závisí na tom, které vlákno se provede jako první. Klasický příklad: dvě vlákna zvyšují čítač. Operace "čti → zvyš → zapiš" není atomická, takže při současném provedení jeden přírůstek "zmizí". Řešení — použití atomických operací (AtomicInteger, AtomicReference) nebo zámků (Mutex, Semaphore, synchronized).
Deadlock — situace, kdy každé vlákno drží zdroj a čeká na zdroj držený jiným vláknem. Žádné vlákno nemůže pokračovat. Podmínky vzniku: vzájemné vyloučení, držení a čekání, bez preempce, cyklické čekání. Prevence: stanovení jednotného pořadí získávání zámků, použití tryLock s časovým limitem, aplikace Lock-Free algoritmů (ConcurrentHashMap, CopyOnWriteArrayList).
Livelock — vlákna nejsou blokována, ale neustále si "předávají" zdroje, aniž by vykonávala užitečnou práci. Příklad: dva lidé se setkají na chodbě a oba uhnou, pohybujíce se stejným směrem. Starvation — vlákno nezíská přístup ke zdroji, protože ostatní vlákna jej neustále zachytávají. Řešení: spravedlivé zámky (fair locks), priority vláken s opatrností.
K prevenci problémů vícevláknovosti se používají synchronizační primitiva: Mutex (vzájemné vyloučení), Semaphore (omezení počtu současných přístupů), Lock (rozhraní s tryLock), Synchronized (zámek na úrovni JVM), @MainActor (Swift — záruka provedení na hlavním vlákně). Na Androidu je k dispozici také ThreadPool přes Executors.newFixedThreadPool, newCachedThreadPool. Ruční správa fondů je však výsadou starších projektů; v nových projektech je lepší používat korutiny.
| Nástroj | Platforma | Typ | Vlastnosti |
|---|---|---|---|
| DispatchQueue (GCD) | iOS | Fronta úkolů | Sériová/Souběžná, priority QoS, Thread Pool spravován systémem |
| OperationQueue | iOS | Fronta operací | Závislosti, zrušení, maxConcurrentOperationCount |
| Coroutines + Flow | Android | Korutiny | Lehké, strukturovaná souběžnost, StateFlow, Channel |
| RxJava / RxKotlin | Android | Reaktivní tok | Observable, Schedulers, bohatá sada operátorů |
| Combine | iOS | Reaktivní tok | Publisher/Subscriber, integrace se SwiftUI |
| Async/Await + Task | iOS / Android | Asynchronní model | Lineární kód, @MainActor, strukturovaná souběžnost |
Často kladené otázky
Main Thread (vlákno UI) je zodpovědné za vykreslování rozhraní a zpracování dotyků. Background Thread provádí úkoly na pozadí — načítání dat, výpočty, síťovou práci. Blokování Main Thread způsobuje zamrznutí rozhraní (ANR na Androidu, frozen UI na iOS).
Race Condition — závodní stav, kdy dvě vlákna současně přistupují ke sdíleným datům a výsledek závisí na pořadí provedení. Vyhnete se jí synchronizací: Mutex, Semaphore, Lock, Synchronized, @MainActor nebo atomickými operacemi.
Coroutines jsou moderním standardem pro Android (JetBrains, podporováno Google). RxJava/RxKotlin je reaktivní přístup s bohatou sadou operátorů. Coroutines jsou jednodušší pro asynchronní volání, RxJava je výkonnější pro složité datové toky. Ve společnosti IT Sectr používáme Coroutines + Flow pro nové projekty.
Deadlock — vzájemné uváznutí, kdy dvě vlákna čekají na zdroje toho druhého. Livelock — vlákna nejsou blokována, ale neustále si předávají zdroje bez užitečné práce. Oba problémy se řeší správným pořadím zámků a časovými limity.
DispatchQueue je abstrakce Grand Central Dispatch (GCD) pro správu vláken. Main Queue provádí úkoly na hlavním vlákně, Global Queues — na vláknech na pozadí. Serial Queue zaručuje sekvenční provedení, Concurrent Queue — paralelní. V moderních projektech je GCD často nahrazováno Async/Await a Task.
Shrnutí
Vyvineme mobilní aplikaci na klíč
IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.