Multithreading și Concurrență în dezvoltarea mobilă: Ce este, principii și cum funcționează

Autor: IT Sectr Publicat: 2026-03-12 Timp de citire: 13 min

Fiecare aplicație mobilă efectuează multe sarcini simultan: încarcă date din rețea, procesează atingerile utilizatorului, animează interfața și salvează fișiere. Dacă tot acest cod rulează într-un singur fir, aplicația îngheață la orice întârziere de rețea. Multithreading și concurența sunt concepte cheie care permit aplicației să rămână receptivă și eficientă. În acest articol vom acoperi toate instrumentele principale: de la Main Thread și RunLoop până la corutinele Kotlin și Combine pe iOS. Materialul se bazează pe documentația oficială Apple GCD.

Puncte Cheie

  • Main Thread — singurul fir pentru lucrul cu UI; toate celelalte sarcini sunt mutate în fundal
  • GCD și OperationQueue — principalele mecanisme de multithreading în iOS
  • Coroutines și Flow — standardul modern de asincronie în Kotlin/Android
  • RxJava, RxSwift și Combine — framework-uri reactive pentru lucrul cu fluxuri de date
  • Race Condition, Deadlock și Livelock — probleme clasice de multithreading care necesită sincronizare
  • Alegerea instrumentului depinde de platformă și complexitatea sarcinii: pentru apeluri simple este suficient Async/Await, pentru fluxuri complexe — Rx sau Combine

Ce este multithreading?

Multithreading este capacitatea unei aplicații de a executa mai multe fragmente de cod simultan. Fiecare fragment rulează într-un fir (Thread) separat — un proces ușor cu propria stivă de apeluri. În dezvoltarea mobilă, firele se împart în două categorii: Main Thread (fir UI) și Background Threads (fire de fundal).

Sistemul de operare gestionează el însuși distribuirea firelor pe nucleele procesorului. Dispozitivele moderne au 6–8 nuclee, deci execuția paralelă poate accelera lucrul. Cu toate acestea, crearea de fire este o operație costisitoare, deci nu se recomandă lucrul direct cu Thread. În schimb, se folosesc abstractizări de nivel superior: DispatchQueue, OperationQueue, CoroutineDispatcher.

Concurența (Concurrency) este un concept mai larg decât multithreading. Concurența înseamnă că sarcinile pot fi executate „simultan” chiar și pe un singur nucleu prin schimbarea contextului. Asincronia (Async/Await) este un model de programare în care o sarcină nu blochează un fir, ci returnează controlul în timp ce așteaptă un rezultat. Limbajele moderne (Kotlin, Swift, Dart) au suport încorporat pentru Async/Await.

La IT Sectr, acordăm o atenție deosebită arhitecturii corecte de multithreading la începutul proiectului. Greșelile făcute într-un stadiu incipient duc la erori dificil de depistat: curse de date, blocaje mutuale și instabilitate a aplicației sub sarcină. Fiecare proiect al nostru trece printr-o revizuire a arhitecturii de concurență în faza de planificare.

Fire principale (Main/Background)

Main Thread (firul principal) — singurul fir dintr-o aplicație mobilă care are acces la UI. Pe Android se numește UI Thread, pe iOS — Main Thread. Toate operațiile cu interfața — modificarea textului, animații, procesarea atingerilor — se efectuează doar pe Main Thread. Dacă pe firul principal se execută o operație grea (încărcare fișier, parsare JSON), interfața nu mai răspunde. Pe Android aceasta duce la ANR (Application Not Responding), pe iOS — la „înghețarea” ecranului.

Background Threads (firele de fundal) sunt destinate pentru tot ce nu ține de UI: cereri de rețea, operații cu baza de date, procesare de imagini, criptografie. După finalizare, rezultatul este transmis la Main Thread pentru afișare. Fiecare platformă oferă propriile instrumente pentru comutarea între fire: DispatchQueue.main.async pe iOS, runOnUiThread sau withContext(Dispatchers.Main) pe Android.

RunLoop — bucla de procesare a evenimentelor pe firul principal iOS. RunLoop așteaptă evenimente (atingeri, temporizatoare, notificări) și le distribuie către gestionarii corespunzători. Pe Android echivalentul este Looper, asociat cu fiecare Main Thread. Main Looper extrage mesaje infinit din coadă și le transmite Handler-ului pentru procesare. Înțelegerea RunLoop și Looper ajută la evitarea scurgerilor de memorie și a „bâlbâielii” interfeței.

GCD și OperationQueue (iOS)

Grand Central Dispatch (GCD) — biblioteca Apple pentru gestionarea multithreading-ului la nivelul limbajului C. GCD lucrează cu DispatchQueue — cozi de sarcini. Dezvoltatorul nu creează fire manual; GCD gestionează un pool de fire (Thread Pool), distribuind sarcinile pe nucleele disponibile ale procesorului. DispatchQueue sunt de două tipuri: Serial Queue (coadă serială — sarcinile se execută una după alta) și Concurrent Queue (coadă concurentă — sarcinile se pot executa simultan).

Main DispatchQueue — o coadă serială legată de firul principal. Global Queues — cozi concurente cu priorități diferite (QoS — Quality of Service): userInteractive, userInitiated, utility, background. Alegerea QoS corecte este esențială pentru performanță: .userInteractive — pentru sarcini care afectează UI (animații, randare); .background — pentru sarcini necritice ca timp (sincronizare, curățare cache).

OperationQueue — o abstractizare peste GCD cu capacități suplimentare: anularea sarcinilor, stabilirea dependențelor între operații, controlul numărului maxim de operații concurente. Operațiile sunt obiecte ale clasei Operation (sau BlockOperation). Exemplu: dacă trebuie să încărcați o imagine, apoi să aplicați un filtru și abia apoi să o afișați — OperationQueue cu dependențe se descurcă perfect. În GCD ar trebui să sincronizați manual acești pași folosind DispatchGroup sau semafor.

Async/Await în Swift 5.5+ — o alternativă modernă la GCD. Cuvintele cheie async și await fac codul asincron liniar și lizibil. Funcțiile sunt marcate ca async, iar apelurile sunt așteptate prin await. Sistemul însuși gestionează comutarea contextului: în mod implicit, o funcție async se execută pe un fir de fundal, în timp ce actualizarea UI se execută pe MainActor. @MainActor — un atribut care garantează execuția codului pe firul principal.

Coroutines și Flow (Kotlin)

Coroutines (corutinele) — fire ușoare pentru Kotlin dezvoltate de JetBrains. Spre deosebire de firele obișnuite, corutinele nu sunt legate de un anumit Thread. Mii de corutine pot rula pe mai multe fire fără un overhead semnificativ. CoroutineScope gestionează ciclul de viață al corutinelor: viewModelScope este legat de ViewModel, lifecycleScope — de Activity/Fragment. Când domeniul este distrus, toate corutinele copil sunt anulate automat.

Dispatchers determină pe ce pool de fire se execută corutina: Dispatchers.Main — fir UI; Dispatchers.IO — pentru cereri de rețea și operații pe disc; Dispatchers.Default — pentru calcule intensive CPU. Pentru a schimba dispatcherul se folosește withContext. Corutinele suportă concurența structurată: fiecare corutină are un părinte, iar când părintele este anulat, toate corutinele copil sunt anulate. Aceasta previne scurgerile de memorie și sarcinile suspendate.

Flow — un flux de date asincron rece din biblioteca de corutine. Flow emite valori secvențial: (1) producătorul generează date, (2) operatorii transformă fluxul, (3) colectorul consumă rezultatul. Spre deosebire de LiveData, Flow suportă lanțuri complexe de operatori (map, filter, flatMapConcat, catch) și este complet sigur pentru fire. StateFlow și SharedFlow — variante fierbinți ale Flow, ideale pentru starea UI și evenimente unice (Snackbar, navigare).

Channel — o altă abstractizare de corutine pentru transmiterea datelor între corutine. Channel funcționează ca o coadă: un expeditor (send) și unul sau mai mulți destinatari (receive). Canalele cu buffer (Channel(UNLIMITED), Channel(BUFFERED)) permit configurarea comportamentului la debordare. Channel este adesea folosit cu Flow pentru a face punte între API-urile bazate pe callback și corutine: callbackFlow { … }.

La IT Sectr folosim activ corutinele și Flow în toate proiectele Android. Aceasta permite scrierea de cod asincron care arată sincron, este ușor de testat (runTest, TestDispatcher) și nu necesită gestionare manuală a firelor. Exemplu de corutină simplă cu încărcare de date:

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 și Combine

Programarea reactivă — un paradigm în care datele se propagă ca fluxuri asincrone (Observable, Publisher). RxJava/RxKotlin — cea mai populară implementare pentru Android, portată din .NET Rx. RxSwift — o bibliotecă similară pentru iOS. Componente principale: Observable (sursă de evenimente), Observer (abonat), Scheduler (gestionare fire), Operators (transformare flux).

Combine — un framework Apple pentru programare reactivă introdus în iOS 13. Combine folosește protocoalele Publisher (editor) și Subscriber (abonat). Spre deosebire de RxSwift, Combine este încorporat în SDK și strâns integrat cu SwiftUI. Operatori în Combine: map, filter, combineLatest, zip, debounce, throttle — acoperă majoritatea scenariilor: de la legarea datelor la UI până la debounce-ul interogării de căutare.

Future și Promise — modele pentru lucrul cu un singur rezultat asincron. Future reprezintă o valoare care va fi disponibilă mai târziu. Promise este o promisiune de a furniza o valoare. În Rx aceasta este Single (un răspuns reușit sau o eroare), în Combine — Future Publisher. În practică, Future/Promise sunt convenabile pentru cereri unice către API, în timp ce Observable/Publisher — pentru fluxuri continue (geolocalizare, introducere text).

Callback și Delegate — modele clasice pentru operații asincrone. Callback — o funcție transmisă ca argument și apelată la finalizarea operației. Delegate — un obiect care implementează un protocol cu metode de gestionare a evenimentelor. Dezavantaj: „callback hell” (callback-uri imbricate) și complexitatea gestionării erorilor. NotificationCenter (iOS) și EventBus (Android) — mecanisme de difuzare a evenimentelor, utile pentru comunicare slab cuplată, dar care duc la dependențe implicite.

Probleme de multithreading (Race Condition, Deadlock)

Multithreading-ul deschide ușa către performanțe înalte, dar în același timp creează riscul unor erori dificil de depistat. Cele mai comune: Race Condition (condiție de cursă), Deadlock (blocare mutuală), Livelock (blocare activă) și Starvation (înfo­me­tarea firului). Înțelegerea acestor probleme este o abilitate esențială pentru orice dezvoltator mobil.

Race Condition

Race Condition apare atunci când două sau mai multe fire citesc și scriu simultan aceleași date fără sincronizare. Rezultatul depinde de care fir se execută primul. Exemplu clasic: două fire incrementează un contor. Operația „citește → incrementează → scrie” nu este atomică, deci atunci când este executată simultan, un increment se „pierde”. Soluția — utilizarea operațiilor atomice (AtomicInteger, AtomicReference) sau a blocărilor (Mutex, Semaphore, synchronized).

Deadlock

Deadlock — o situație în care fiecare fir deține o resursă și așteaptă o resursă deținută de un alt fir. Niciun fir nu poate continua. Condiții de apariție: excludere mutuală, deținere și așteptare, fără preempțiune, așteptare circulară. Prevenire: stabilirea unui singur ordin de achiziție a blocărilor, utilizarea tryLock cu timeout, aplicarea algoritmilor Lock-Free (ConcurrentHashMap, CopyOnWriteArrayList).

Livelock și Starvation

Livelock — firele nu sunt blocate, dar își „pasă” în mod constant resurse una alteia fără a efectua muncă utilă. Exemplu: două persoane se întâlnesc într-un coridor și ambele fac loc, mișcându-se în aceeași direcție. Starvation — un fir nu obține acces la o resursă deoarece alte fire o interceptează constant. Soluție: blocări echitabile (fair locks), priorități de fire cu prudență.

Instrumente de sincronizare

Pentru prevenirea problemelor de multithreading se folosesc primitive de sincronizare: Mutex (excludere mutuală), Semaphore (limitarea numărului de accesări simultane), Lock (interfață cu tryLock), Synchronized (blocare la nivel JVM), @MainActor (Swift — garanția execuției pe firul principal). Pe Android este disponibil și ThreadPool (pool de fire) prin Executors.newFixedThreadPool, newCachedThreadPool. Cu toate acestea, gestionarea manuală a pool-urilor este prerogativa proiectelor moștenite; în proiectele noi este mai bine să se folosească corutine.

Instrument Platformă Tip Caracteristici
DispatchQueue (GCD)iOSCoadă de sarciniSerială/Concurentă, priorități QoS, Thread Pool gestionat de sistem
OperationQueueiOSCoadă de operațiiDependențe, anulare, maxConcurrentOperationCount
Coroutines + FlowAndroidCorutineUșoare, concurență structurată, StateFlow, Channel
RxJava / RxKotlinAndroidFlux reactivObservable, Schedulers, set bogat de operatori
CombineiOSFlux reactivPublisher/Subscriber, integrare SwiftUI
Async/Await + TaskiOS / AndroidModel asincronCod liniar, @MainActor, concurență structurată

Întrebări frecvente

Care este diferența dintre Main Thread și Background Thread?

Main Thread (fir UI) este responsabil pentru randarea interfeței și procesarea atingerilor. Background Thread execută sarcini în fundal — încărcare date, calcule, lucru în rețea. Blocarea Main Thread cauzează înghețarea interfeței (ANR pe Android, frozen UI pe iOS).

Ce este Race Condition și cum să o evităm?

Race Condition — o condiție de cursă când două fire accesează simultan date partajate și rezultatul depinde de ordinea de execuție. Se evită prin sincronizare: Mutex, Semaphore, Lock, Synchronized, @MainActor sau operații atomice.

Coroutines sau RxJava: ce să alegem pentru Android?

Coroutines este standardul modern pentru Android (JetBrains, acceptat de Google). RxJava/RxKotlin este o abordare reactivă cu un set bogat de operatori. Coroutines sunt mai simple pentru apeluri asincrone, RxJava este mai puternic pentru fluxuri de date complexe. La IT Sectr folosim Coroutines + Flow pentru proiecte noi.

Ce sunt Deadlock și Livelock?

Deadlock — o blocare mutuală în care două fire așteaptă resursele unul altuia. Livelock — firele nu sunt blocate, dar își transferă constant resurse fără a face muncă utilă. Ambele probleme se rezolvă prin ordinea corectă a blocărilor și timeout-uri.

De ce este necesar DispatchQueue în iOS?

DispatchQueue este o abstractizare a Grand Central Dispatch (GCD) pentru gestionarea firelor. Main Queue execută sarcini pe firul principal, Global Queues — pe fire de fundal. Serial Queue garantează execuția secvențială, Concurrent Queue — paralelă. În proiectele moderne, GCD este adesea înlocuit de Async/Await și Task.

Rezumat

  • Main Thread — doar UI; toate celelalte operații în fundal
  • GCD și OperationQueue — baza multithreading-ului pe iOS; Async/Await — alternativa modernă
  • Coroutines și Flow — standardul pentru Android; concurența structurată previne scurgerile
  • RxJava, RxSwift, Combine — framework-uri reactive pentru fluxuri de date complexe
  • Race Condition și Deadlock — principalele probleme; rezolvate prin blocări și ordinea corectă de achiziție a resurselor
  • Thread Pool este gestionat de sistem (GCD) sau framework (corutine); crearea manuală de fire nu este recomandată
  • Alegerea instrumentului depinde de platformă: Coroutines pentru Android, GCD/Combine pentru iOS

Vom dezvolta o aplicație mobilă la cheie

IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.

Discutați proiectul