Többszálúság és Párhuzamosság a mobilfejlesztésben: mi ez, elvek és hogyan működik

Szerző: IT Sectr Megjelenés: 2026-03-12 Olvasási idő: 13 perc

Minden mobilalkalmazás egyszerre több feladatot is végrehajt: adatokat tölt be a hálózatról, feldolgozza a felhasználói érintéseket, animálja a felületet és fájlokat ment. Ha ez a kód egyetlen szálon fut, az alkalmazás bármilyen hálózati késleltetésnél lefagy. A többszálúság (multithreading) és a párhuzamosság olyan kulcsfontosságú fogalmak, amelyek lehetővé teszik az alkalmazás számára, hogy érzékeny és hatékony maradjon. Ebben a cikkben áttekintjük az összes fő eszközt: a Main Thread-tól és RunLoop-tól a Kotlin-korutinokig és az iOS Combine-ig. Az anyag az Apple hivatalos GCD dokumentációján alapul.

Főbb Pontok

  • Main Thread — az egyetlen szál a UI-val való munkához; az összes többi feladat a háttérbe kerül
  • GCD és OperationQueue — a többszálúság fő mechanizmusai iOS-ben
  • Coroutines és Flow — az aszinkronitás modern szabványa Kotlin/Android rendszerben
  • RxJava, RxSwift és Combine — reaktív keretrendszerek adatfolyamokkal való munkához
  • Race Condition, Deadlock és Livelock — klasszikus többszálúsági problémák, amelyek szinkronizációt igényelnek
  • Az eszköz kiválasztása a platformtól és a feladat összetettségétől függ: egyszerű hívásokhoz az Async/Await elegendő, összetett folyamokhoz — Rx vagy Combine

Mi a többszálúság?

A többszálúság egy alkalmazás azon képessége, hogy egyszerre több kódrészletet hajtson végre. Minden egyes rész egy külön szálon (Thread) fut — egy könnyű folyamaton, saját hívási veremmel. A mobilfejlesztésben a szálak két kategóriába sorolhatók: Main Thread (UI szál) és Background Threads (háttérszálak).

Az operációs rendszer maga kezeli a szálak elosztását a processzormagok között. A modern eszközök 6–8 maggal rendelkeznek, így a párhuzamos végrehajtás valóban felgyorsíthatja a munkát. A szálak létrehozása azonban költséges művelet, ezért a közvetlen Thread használata nem ajánlott. Ehelyett magasabb szintű absztrakciókat használnak: DispatchQueue, OperationQueue, CoroutineDispatcher.

A párhuzamosság (Concurrency) tágabb fogalom, mint a többszálúság. A párhuzamosság azt jelenti, hogy a feladatok „egyidejűleg" is végrehajthatók akár egyetlen magon is a kontextusváltás révén. Az aszinkronitás (Async/Await) egy olyan programozási modell, ahol egy feladat nem blokkol egy szálat, hanem visszaadja a vezérlést, miközben az eredményre vár. A modern nyelvek (Kotlin, Swift, Dart) beépített támogatással rendelkeznek az Async/Await számára.

Az IT Sectr-nél kiemelt figyelmet fordítunk a megfelelő többszálúsági architektúrára a projekt elején. A korai szakaszban elkövetett hibák nehezen felderíthető hibákhoz vezetnek: adatversenyek, holtpontok és az alkalmazás instabilitása terhelés alatt. Minden projektünk egy párhuzamossági architektúra-felülvizsgálaton megy keresztül a tervezési szakaszban.

Fő szálak (Main/Background)

Main Thread (fő szál) — az egyetlen szál egy mobilalkalmazásban, amely hozzáfér a UI-hoz. Androidon UI Thread-nek hívják, iOS-en — Main Thread. Az összes felületi művelet — szöveg megváltoztatása, animációk, érintések feldolgozása — csak a Main Thread-en történik. Ha a fő szálon egy nehéz művelet (fájlbetöltés, JSON elemzés) fut, a felület leáll. Androidon ez ANR-hez (Application Not Responding) vezet, iOS-en — a képernyő „lefagyásához".

A Background Threads (háttérszálak) minden olyan dologra szolgálnak, ami nem kapcsolódik a UI-hoz: hálózati kérések, adatbázis-műveletek, képfeldolgozás, kriptográfia. Befejezés után az eredmény átkerül a Main Thread-re megjelenítésre. Minden platform saját eszközöket biztosít a szálak közötti váltáshoz: DispatchQueue.main.async iOS-en, runOnUiThread vagy withContext(Dispatchers.Main) Androidon.

RunLoop — az eseményfeldolgozási ciklus az iOS fő szálán. A RunLoop eseményekre vár (érintések, időzítők, értesítések), és továbbítja azokat a megfelelő kezelőknek. Androidon a megfelelője a Looper, amely minden Main Thread-hez kapcsolódik. A Main Looper végtelenül kivonja az üzeneteket a sorból, és továbbítja azokat a Handler-nek feldolgozásra. A RunLoop és a Looper megértése segít elkerülni a memóriaszivárgásokat és a felület „dadogását".

GCD és OperationQueue (iOS)

Grand Central Dispatch (GCD) — egy Apple könyvtár a többszálúság kezelésére C nyelvi szinten. A GCD DispatchQueue — feladat sorokkal dolgozik. A fejlesztő nem hoz létre manuálisan szálakat; a GCD egy szálkészletet (Thread Pool) kezel, elosztva a feladatokat a rendelkezésre álló processzormagok között. A DispatchQueue két típusú: Serial Queue (soros sor — a feladatok egymás után hajtódnak végre) és Concurrent Queue (párhuzamos sor — a feladatok egyszerre is végrehajthatók).

Main DispatchQueue — egy soros sor, amely a fő szálhoz van kötve. Global Queues — párhuzamos sorok különböző prioritásokkal (QoS — Quality of Service): userInteractive, userInitiated, utility, background. A megfelelő QoS kiválasztása kritikus a teljesítmény szempontjából: .userInteractive — a UI-t befolyásoló feladatokhoz (animációk, renderelés); .background — idő szempontjából nem kritikus feladatokhoz (szinkronizálás, gyorsítótár tisztítása).

OperationQueue — egy absztrakció a GCD felett, további képességekkel: feladatok megszakítása, függőségek beállítása a műveletek között, a párhuzamos műveletek maximális számának szabályozása. A műveletek az Operation osztály (vagy BlockOperation) objektumai. Példa: ha be kell töltenie egy képet, majd alkalmaznia egy szűrőt, és csak azután megjelenítenie — az OperationQueue függőségekkel tökéletesen kezeli. A GCD-ben manuálisan kellene szinkronizálnia ezeket a lépéseket a DispatchGroup vagy szemafór segítségével.

Async/Await a Swift 5.5+ -ban — egy modern alternatíva a GCD-hez. Az async és await kulcsszavak lineárissá és olvashatóvá teszik az aszinkron kódot. A függvények async-ként vannak megjelölve, és a hívások await segítségével várják be. A rendszer maga kezeli a kontextusváltást: alapértelmezés szerint egy async függvény egy háttérszálon fut, míg a UI frissítése a MainActor-on fut. @MainActor — egy attribútum, amely garantálja a kód végrehajtását a fő szálon.

Coroutines és Flow (Kotlin)

Coroutines (korutinok) — könnyű szálak Kotlinhoz, a JetBrains által fejlesztve. A hagyományos szálaktól eltérően a korutinok nincsenek egy adott Thread-hez kötve. Több ezer korutin futhat több szálon jelentős többletterhelés nélkül. CoroutineScope kezeli a korutinok életciklusát: a viewModelScope a ViewModel-hez van kötve, a lifecycleScope — az Activity/Fragment-hez. Amikor a hatókör megsemmisül, az összes gyermek korutin automatikusan törlődik.

A Dispatchers határozzák meg, hogy a korutin melyik szálkészleten fusson: Dispatchers.Main — UI szál; Dispatchers.IO — hálózati kérések és lemezműveletek számára; Dispatchers.Default — CPU-igényes számításokhoz. A dispatcher váltásához a withContext használható. A korutinok támogatják a strukturált párhuzamosságot: minden korutinnak van egy szülője, és amikor a szülő törlődik, az összes gyermek korutin törlődik. Ez megakadályozza a memóriaszivárgásokat és a függőben lévő feladatokat.

Flow — egy hideg aszinkron adatfolyam a korutin könyvtárból. A Flow szekvenciálisan bocsát ki értékeket: (1) a termelő adatokat generál, (2) az operátorok átalakítják a folyamot, (3) a gyűjtő felhasználja az eredményt. A LiveData-val ellentétben a Flow támogatja az összetett operátor láncokat (map, filter, flatMapConcat, catch), és teljesen szálbiztos. StateFlow és SharedFlow — a Flow forró változatai, ideálisak a UI állapotához és egyszeri eseményekhez (Snackbar, navigáció).

Channel — egy másik korutin absztrakció az adatok korutinok közötti átvitelére. A Channel úgy működik, mint egy sor: egy feladó (send) és egy vagy több vevő (receive). A pufferelt csatornák (Channel(UNLIMITED), Channel(BUFFERED)) lehetővé teszik a viselkedés konfigurálását túlcsordulás esetén. A Channel gyakran a Flow-val együtt használatos a callback-alapú API-k korutinokhoz való kötéséhez: callbackFlow { … }.

Az IT Sectr-nél aktívan használjuk a korutinokat és a Flow-t minden Android projektben. Ez lehetővé teszi aszinkron kód írását, amely szinkronnak tűnik, könnyen tesztelhető (runTest, TestDispatcher), és nem igényel manuális szálkezelést. Példa egy egyszerű korutinra adatbetöltéssel:

kotlin
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()
        }
    }
}

Rx és Combine

Reaktív programozás — egy paradigma, ahol az adatok aszinkron folyamokként (Observable, Publisher) terjednek. RxJava/RxKotlin — a legnépszerűbb megvalósítás Androidra, a .NET Rx-ből portolva. RxSwift — egy hasonló könyvtár iOS-re. Fő összetevők: Observable (eseményforrás), Observer (feliratkozó), Scheduler (szálkezelés), Operators (folyam-átalakítás).

Combine — egy Apple keretrendszer a reaktív programozáshoz, amelyet az iOS 13-ban vezettek be. A Combine a Publisher (kiadó) és Subscriber (feliratkozó) protokollokat használja. Az RxSwift-tel ellentétben a Combine be van építve az SDK-ba, és szorosan integrálódik a SwiftUI-val. Operátorok a Combine-ban: map, filter, combineLatest, zip, debounce, throttle — lefedik a legtöbb forgatókönyvet: az adatok UI-hoz kötésétől a keresési lekérdezés debounce-áig.

Future és Promise — minták egyetlen aszinkron eredménnyel való munkához. A Future egy olyan értéket képvisel, amely később lesz elérhető. A Promise egy ígéret az érték biztosítására. Az Rx-ben ez a Single (egy sikeres válasz vagy hiba), a Combine-ban — Future Publisher. A gyakorlatban a Future/Promise kényelmes egyedi API-kérésekhez, míg az Observable/Publisher — folyamatos adatfolyamokhoz (helymeghatározás, szövegbevitel).

Callback és Delegate — klasszikus minták aszinkron műveletekhez. Callback — egy függvény, amelyet argumentumként adnak át, és a művelet befejezésekor hívják meg. Delegate — egy objektum, amely egy protokollt valósít meg eseménykezelő metódusokkal. Hátrány: „callback pokol" (beágyazott callback-ek) és a hibakezelés összetettsége. NotificationCenter (iOS) és EventBus (Android) — esemény-szórási mechanizmusok, hasznosak a laza csatolású kommunikációhoz, de implicit függőségekhez vezetnek.

Többszálúsági problémák (Race Condition, Deadlock)

A többszálúság ajtót nyit a nagy teljesítmény felé, ugyanakkor kockázatot teremt a nehezen felderíthető hibákra. A leggyakoribbak: Race Condition (versenyhelyzet), Deadlock (holtpont), Livelock (aktív holtpont) és Starvation (szál éhezés). E problémák megértése kötelező készség minden mobilfejlesztő számára.

Race Condition

A Race Condition akkor fordul elő, amikor két vagy több szál egyszerre olvassa és írja ugyanazokat az adatokat szinkronizáció nélkül. Az eredmény attól függ, hogy melyik szál hajtódik végre először. Klasszikus példa: két szál növel egy számlálót. A „olvas → növel → ír" művelet nem atomi, így egyidejű végrehajtáskor egy növekmény „elveszik". Megoldás — atomi műveletek (AtomicInteger, AtomicReference) vagy zárak (Mutex, Semaphore, synchronized) használata.

Deadlock

Deadlock — olyan helyzet, amikor minden szál tart egy erőforrást, és egy másik szál által tartott erőforrásra vár. Egyik szál sem folytathatja. Kialakulási feltételek: kölcsönös kizárás, tartás és várakozás, nincs megelőzés, körkörös várakozás. Megelőzés: egységes zár megszerzési sorrend felállítása, tryLock használata időkorláttal, Lock-Free algoritmusok alkalmazása (ConcurrentHashMap, CopyOnWriteArrayList).

Livelock és Starvation

Livelock — a szálak nincsenek blokkolva, de folyamatosan "passzolják" egymásnak az erőforrásokat hasznos munka végzése nélkül. Példa: két ember találkozik egy folyosón, és mindketten félreállnak, ugyanabba az irányba mozogva. Starvation — egy szál nem fér hozzá egy erőforráshoz, mert más szálak folyamatosan elkapják. Megoldás: tisztességes zárak (fair locks), szálprioritások óvatossággal.

Szinkronizációs eszközök

A többszálúsági problémák megelőzésére szinkronizációs primitíveket használnak: Mutex (kölcsönös kizárás), Semaphore (az egyidejű hozzáférések számának korlátozása), Lock (interfész tryLock-kal), Synchronized (JVM szintű zár), @MainActor (Swift — garancia a fő szálon történő végrehajtásra). Androidon a ThreadPool is elérhető az Executors.newFixedThreadPool, newCachedThreadPool segítségével. A manuális készletkezelés azonban a régi projektek kiváltsága; az új projektekben jobb korutinokat használni.

Eszköz Platform Típus Jellemzők
DispatchQueue (GCD)iOSFeladatsorSoros/Párhuzamos, QoS prioritások, Thread Pool rendszer által kezelve
OperationQueueiOSMűveletsorFüggőségek, megszakítás, maxConcurrentOperationCount
Coroutines + FlowAndroidKorutinokKönnyű, strukturált párhuzamosság, StateFlow, Channel
RxJava / RxKotlinAndroidReaktív folyamObservable, Schedulers, gazdag operátorkészlet
CombineiOSReaktív folyamPublisher/Subscriber, SwiftUI integráció
Async/Await + TaskiOS / AndroidAszinkron modellLineáris kód, @MainActor, strukturált párhuzamosság

Gyakran Ismételt Kérdések

Mi a különbség a Main Thread és a Background Thread között?

A Main Thread (UI szál) felelős a felület megjelenítéséért és az érintések feldolgozásáért. A Background Thread háttérfeladatokat végez — adatbetöltés, számítások, hálózati munka. A Main Thread blokkolása a felület lefagyását okozza (ANR Androidon, frozen UI iOS-en).

Mi a Race Condition és hogyan kerüljük el?

Race Condition — versenyhelyzet, amikor két szál egyszerre fér hozzá megosztott adatokhoz, és az eredmény a végrehajtás sorrendjétől függ. Szinkronizációval kerülhető el: Mutex, Semaphore, Lock, Synchronized, @MainActor vagy atomi műveletek.

Coroutines vagy RxJava: mit válasszunk Androidra?

A Coroutines a modern szabvány Androidra (JetBrains, Google támogatja). Az RxJava/RxKotlin egy reaktív megközelítés gazdag operátorkészlettel. A Coroutines egyszerűbb az aszinkron hívásokhoz, az RxJava erősebb az összetett adatfolyamokhoz. Az IT Sectr-nél a Coroutines + Flow-t használjuk új projektekhez.

Mi a Deadlock és a Livelock?

Deadlock — kölcsönös blokkolás, amikor két szál egymás erőforrásaira vár. Livelock — a szálak nincsenek blokkolva, de folyamatosan passzolják az erőforrásokat hasznos munka nélkül. Mindkét probléma megfelelő zárási sorrenddel és időtúllépésekkel oldható meg.

Miért van szükség DispatchQueue-ra iOS-ben?

A DispatchQueue a Grand Central Dispatch (GCD) absztrakciója a szálkezeléshez. A Main Queue a fő szálon hajtja végre a feladatokat, a Global Queues — háttérszálakon. A Serial Queue garantálja a szekvenciális végrehajtást, a Concurrent Queue — a párhuzamost. A modern projektekben a GCD-t gyakran felváltja az Async/Await és a Task.

Összefoglaló

  • Main Thread — csak UI; az összes többi művelet a háttérben
  • GCD és OperationQueue — a többszálúság alapja iOS-ben; Async/Await — a modern alternatíva
  • Coroutines és Flow — az Android szabványa; a strukturált párhuzamosság megakadályozza a szivárgásokat
  • RxJava, RxSwift, Combine — reaktív keretrendszerek összetett adatfolyamokhoz
  • Race Condition és Deadlock — a fő problémák; zárakkal és megfelelő erőforrás-megszerzési sorrenddel oldhatók meg
  • Thread Pool kezelése a rendszer (GCD) vagy a keretrendszer (korutin) által történik; a manuális szál létrehozás nem ajánlott
  • Az eszköz kiválasztása a platformtól függ: Coroutines Androidhoz, GCD/Combine iOS-hez

Kulcsrakész mobilalkalmazást fejlesztünk

Az IT Sectr 2017 óta készít iOS és Android alkalmazásokat induló vállalkozásoknak és vállalkozásoknak. Tanácsot adunk, és a legjobb megoldást javasoljuk.

Projekt megbeszélése