Multithreading at Concurrency sa Mobile Development: Ano Ito, Mga Prinsipyo at Paano Ito Gumagana

May-akda: IT Sectr Nai-publish: 2026-03-12 Oras ng pagbabasa: 13 min

Bawat mobile application ay nagsasagawa ng maraming gawain nang sabay-sabay: naglo-load ng data mula sa network, nagpoproseso ng mga pagpindot ng user, nag-a-animate ng interface at nagse-save ng mga file. Kung ang lahat ng code na ito ay tumatakbo sa isang thread, ang application ay mag-freeze sa anumang pagkaantala ng network. Ang Multithreading at concurrency ay mga pangunahing konsepto na nagpapahintulot sa application na manatiling tumutugon at mahusay. Sa artikulong ito ay tatalakayin natin ang lahat ng pangunahing tool: mula sa Main Thread at RunLoop hanggang sa Kotlin coroutines at Combine sa iOS. Ang materyal ay batay sa opisyal na dokumentasyon ng Apple GCD.

Mahahalagang Punto

  • Main Thread — ang tanging thread para sa pagtatrabaho sa UI; lahat ng iba pang gawain ay inililipat sa Background
  • GCD at OperationQueue — ang pangunahing mekanismo ng multithreading sa iOS
  • Coroutines at Flow — ang modernong pamantayan para sa asynchrony sa Kotlin/Android
  • RxJava, RxSwift at Combine — reaktibong framework para sa pagtatrabaho sa mga stream ng data
  • Race Condition, Deadlock at Livelock — mga klasikong problema ng multithreading na nangangailangan ng synchronization
  • Ang pagpili ng tool ay depende sa platform at pagiging kumplikado ng gawain: para sa mga simpleng tawag, sapat na ang Async/Await, para sa mga kumplikadong stream — Rx o Combine

Ano ang Multithreading?

Ang multithreading ay ang kakayahan ng isang application na magsagawa ng maraming piraso ng code nang sabay-sabay. Ang bawat piraso ay tumatakbo sa isang hiwalay na thread — isang magaan na proseso na may sariling stack ng tawag. Sa pag-unlad ng mobile, ang mga thread ay nahahati sa dalawang kategorya: Main Thread (UI thread) at Background Threads (mga thread sa likuran).

Ang operating system mismo ang namamahala sa pamamahagi ng mga thread sa mga core ng processor. Ang mga modernong device ay may 6–8 core, kaya ang parallel execution ay maaaring mapabilis ang trabaho. Gayunpaman, ang paggawa ng mga thread ay isang mamahaling operasyon, kaya ang direktang pagtatrabaho sa Thread ay hindi inirerekomenda. Sa halip, ginagamit ang mas mataas na antas ng abstraction: DispatchQueue, OperationQueue, CoroutineDispatcher.

Ang concurrency ay isang mas malawak na konsepto kaysa sa multithreading. Ang concurrency ay nangangahulugan na ang mga gawain ay maaaring isagawa "nang sabay-sabay" kahit sa isang core sa pamamagitan ng paglipat ng konteksto. Ang Asynchrony (Async/Await) ay isang modelo ng programming kung saan ang isang gawain ay hindi humahadlang sa isang thread, ngunit nagbabalik ng kontrol habang naghihintay ng resulta. Ang mga modernong wika (Kotlin, Swift, Dart) ay may built-in na suporta para sa Async/Await.

Sa IT Sectr, binibigyan namin ng espesyal na atensyon ang tamang arkitektura ng multithreading sa pagsisimula ng isang proyekto. Ang mga pagkakamali na ginawa sa maagang yugto ay humahantong sa mga bug na mahirap mahuli: mga lahi ng data, deadlock, at kawalang-tatag ng application sa ilalim ng load. Ang bawat isa sa aming mga proyekto ay sumasailalim sa isang pagsusuri sa arkitektura ng concurrency sa yugto ng pagpaplano.

Pangunahing Thread (Main/Background)

Main Thread (pangunahing thread) — ang tanging thread sa isang mobile application na may access sa UI. Sa Android ito ay tinatawag na UI Thread, sa iOS — Main Thread. Lahat ng operasyon ng interface — pagbabago ng teksto, animation, pagproseso ng pagpindot — ay ginagawa lamang sa Main Thread. Kung ang isang mabigat na operasyon (paglo-load ng file, pag-parse ng JSON) ay isinasagawa sa pangunahing thread, ang interface ay hihinto sa pagtugon. Sa Android ito ay humahantong sa ANR (Application Not Responding), sa iOS — sa isang "nagyeyelong" screen.

Ang Background Threads (mga thread sa likuran) ay para sa lahat ng hindi nauugnay sa UI: mga kahilingan sa network, mga operasyon sa database, pagproseso ng imahe, cryptography. Pagkatapos makumpleto, ang resulta ay ipinapasa sa Main Thread para sa pagpapakita. Ang bawat platform ay nagbibigay ng sarili nitong mga tool para sa paglipat sa pagitan ng mga thread: DispatchQueue.main.async sa iOS, runOnUiThread o withContext(Dispatchers.Main) sa Android.

RunLoop — ang loop ng pagproseso ng kaganapan sa pangunahing thread ng iOS. Ang RunLoop ay naghihintay para sa mga kaganapan (pagpindot, timer, abiso) at ipinapadala ang mga ito sa mga naaangkop na handler. Sa Android ang katumbas ay Looper, na nauugnay sa bawat Main Thread. Ang Main Looper ay walang katapusang kumukuha ng mga mensahe mula sa pila at ipinapasa ang mga ito sa Handler para sa pagproseso. Ang pag-unawa sa RunLoop at Looper ay tumutulong na maiwasan ang pagtagas ng memorya at "pag-utal" ng interface.

GCD at OperationQueue (iOS)

Grand Central Dispatch (GCD) — isang Apple library para sa pamamahala ng multithreading sa antas ng C language. Ang GCD ay gumagana sa DispatchQueue — mga pila ng gawain. Ang developer ay hindi gumagawa ng mga thread nang manu-mano; ang GCD ay namamahala ng isang thread pool, namamahagi ng mga gawain sa mga available na core ng processor. Ang DispatchQueue ay may dalawang uri: Serial Queue (serial queue — ang mga gawain ay isinasagawa nang sunud-sunod) at Concurrent Queue (concurrent queue — ang mga gawain ay maaaring isagawa nang sabay-sabay).

Main DispatchQueue — isang serial queue na nakatali sa pangunahing thread. Global Queues — mga concurrent queue na may iba't ibang priyoridad (QoS — Quality of Service): userInteractive, userInitiated, utility, background. Ang pagpili ng tamang QoS ay kritikal para sa pagganap: .userInteractive — para sa mga gawain na nakakaapekto sa UI (animation, rendering); .background — para sa mga gawain na hindi kritikal sa oras (synchronization, paglilinis ng cache).

OperationQueue — isang abstraction sa ibabaw ng GCD na may mga karagdagang kakayahan: pagkansela ng mga gawain, pagtatakda ng mga dependency sa pagitan ng mga operasyon, pagkontrol sa maximum na bilang ng mga concurrent na operasyon. Ang mga operasyon ay mga bagay ng klase ng Operation (o BlockOperation). Halimbawa: kung kailangan mong mag-load ng isang imahe, pagkatapos ay mag-apply ng isang filter, at pagkatapos lamang ito ipakita — ang OperationQueue na may mga dependency ay humahawak nito nang perpekto. Sa GCD kailangan mong manu-manong i-synchronize ang mga hakbang na ito gamit ang DispatchGroup o semaphore.

Async/Await sa Swift 5.5+ — isang modernong alternatibo sa GCD. Ang mga keyword na async at await ay ginagawang linear at nababasa ang asynchronous code. Ang mga function ay minarkahan bilang async, at ang mga tawag ay hinihintay sa pamamagitan ng await. Ang sistema mismo ang namamahala sa paglipat ng konteksto: bilang default, isang async function ay tumatakbo sa isang background thread, habang ang pag-update ng UI ay tumatakbo sa MainActor. @MainActor — isang attribute na ginagarantiyahan ang pagpapatupad ng code sa pangunahing thread.

Coroutines at Flow (Kotlin)

Coroutines — magaan na mga thread para sa Kotlin na binuo ng JetBrains. Hindi tulad ng mga ordinaryong thread, ang mga coroutine ay hindi nakatali sa isang partikular na Thread. Libu-libong coroutine ang maaaring tumakbo sa maraming thread nang walang makabuluhang overhead. CoroutineScope ang namamahala sa lifecycle ng mga coroutine: ang viewModelScope ay nakatali sa ViewModel, ang lifecycleScope — sa Activity/Fragment. Kapag nawasak ang scope, awtomatikong kinakansela ang lahat ng child coroutine.

Tinutukoy ng Dispatchers kung saang thread pool tatakbo ang coroutine: Dispatchers.Main — UI thread; Dispatchers.IO — para sa mga kahilingan sa network at mga operasyon sa disk; Dispatchers.Default — para sa mga compute na masinsinang CPU. Para magpalit ng dispatcher, ginagamit ang withContext. Sinusuportahan ng mga coroutine ang structured concurrency: bawat coroutine ay may magulang, at kapag kinansela ang magulang, lahat ng child coroutine ay kanselado. Pinipigilan nito ang pagtagas ng memorya at mga nasuspinde na gawain.

Flow — isang malamig na asynchronous na stream ng data mula sa library ng coroutine. Ang Flow ay naglalabas ng mga halaga nang sunud-sunod: (1) ang producer ay gumagawa ng data, (2) ang mga operator ay nagbabago ng stream, (3) ang collector ay kumokonsumo ng resulta. Hindi tulad ng LiveData, sinusuportahan ng Flow ang mga kumplikadong chain ng operator (map, filter, flatMapConcat, catch) at ganap na thread-safe. StateFlow at SharedFlow — mainit na variant ng Flow, perpekto para sa estado ng UI at isang beses na mga kaganapan (Snackbar, navigation).

Channel — isa pang abstraction ng coroutine para sa pagpasa ng data sa pagitan ng mga coroutine. Ang Channel ay gumagana tulad ng isang pila: isang nagpadala (send) at isa o higit pang mga receiver (receive). Ang mga buffered channel (Channel(UNLIMITED), Channel(BUFFERED)) ay nagpapahintulot sa pag-configure ng pag-uugali sa overflow. Ang Channel ay kadalasang ginagamit kasama ng Flow para sa pag-bridge ng callback-based na API sa mga coroutine: callbackFlow { … }.

Sa IT Sectr aktibo kaming gumagamit ng mga coroutine at Flow sa lahat ng Android project. Ito ay nagpapahintulot sa pagsulat ng asynchronous code na mukhang synchronous, madaling subukan (runTest, TestDispatcher) at hindi nangangailangan ng manu-manong pamamahala ng thread. Halimbawa ng isang simpleng coroutine na may pag-load ng data:

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 at Combine

Ang reaktibong programming — isang paradigma kung saan ang data ay kumakalat bilang asynchronous na stream (Observable, Publisher). RxJava/RxKotlin — ang pinakasikat na pagpapatupad para sa Android, na ported mula sa .NET Rx. RxSwift — isang katulad na library para sa iOS. Mga pangunahing bahagi: Observable (pinagmumulan ng kaganapan), Observer (subscriber), Scheduler (pamamahala ng thread), Operators (pagbabago ng stream).

Combine — isang Apple framework para sa reaktibong programming na ipinakilala sa iOS 13. Ang Combine ay gumagamit ng mga protocol na Publisher (tagapaglathala) at Subscriber(tagasuskribe). Hindi tulad ng RxSwift, ang Combine ay binuo sa SDK at malapit na isinama sa SwiftUI. Mga operator sa Combine: map, filter, combineLatest, zip, debounce, throttle — sumasaklaw sa karamihan ng mga senaryo: mula sa pagbubuklod ng data sa UI hanggang sa debounce ng query sa paghahanap.

Future at Promise — mga pattern para sa pagtatrabaho sa isang solong asynchronous na resulta. Ang Future ay kumakatawan sa isang halaga na magiging available mamaya. Ang Promise ay isang pangako na magbigay ng isang halaga. Sa Rx ito ay Single (isang matagumpay na tugon o error), sa Combine — Future Publisher. Sa pagsasagawa, ang Future/Promise ay maginhawa para sa mga solong kahilingan sa API, habang ang Observable/Publisher — para sa tuluy-tuloy na stream (geolocation, text input).

Callback at Delegate — mga klasikong pattern para sa asynchronous na operasyon. Callback — isang function na ipinasa bilang argumento at tinatawag sa pagkumpleto ng operasyon. Delegate — isang bagay na nagpapatupad ng isang protocol na may mga pamamaraan ng handler ng kaganapan. Disadvantage: "callback hell" (nested callback) at pagiging kumplikado ng paghawak ng error. NotificationCenter (iOS) at EventBus (Android) — mga mekanismo ng broadcast ng kaganapan, kapaki-pakinabang para sa maluwag na pinagsamang komunikasyon ngunit humahantong sa implicit na mga dependency.

Mga Problema sa Multithreading (Race Condition, Deadlock)

Ang multithreading ay nagbubukas ng pinto sa mataas na pagganap, ngunit sa parehong oras ay lumilikha ng panganib ng mga error na mahirap mahuli. Ang pinakakaraniwan: Race Condition (kondisyon ng karera), Deadlock (pagkakapatong), Livelock (aktibong pagkakapatong) at Starvation (pagkagutom ng thread). Ang pag-unawa sa mga problemang ito ay isang sapilitan na kasanayan para sa sinumang mobile developer.

Race Condition

Ang Race Condition ay nangyayari kapag ang dalawa o higit pang mga thread ay sabay-sabay na nagbabasa at nagsusulat ng parehong data nang walang synchronization. Ang resulta ay depende sa kung aling thread ang unang isasagawa. Klasikong halimbawa: dalawang thread ang nagtataas ng isang counter. Ang operasyon na "basahin → itaas → isulat" ay hindi atomic, kaya kapag isinagawa nang sabay-sabay, ang isang pagtaas ay "nawawala". Ang solusyon — gumamit ng atomic na operasyon (AtomicInteger, AtomicReference) o mga lock (Mutex, Semaphore, synchronized).

Deadlock

Deadlock — isang sitwasyon kung saan ang bawat thread ay may hawak na isang mapagkukunan at naghihintay para sa isang mapagkukunan na hawak ng isa pang thread. Walang thread ang maaaring magpatuloy. Mga kondisyon ng paglitaw: mutual exclusion, hold at wait, walang preemption, circular wait. Pag-iwas: magtatag ng isang solong pagkakasunud-sunod ng pagkuha ng lock, gumamit ng tryLock na may timeout, mag-apply ng Lock-Free algorithm (ConcurrentHashMap, CopyOnWriteArrayList).

Livelock at Starvation

Livelock — ang mga thread ay hindi naka-block ngunit patuloy na "nagpapasa" ng mga mapagkukunan sa isa't isa nang hindi gumagawa ng kapaki-pakinabang na trabaho. Halimbawa: dalawang tao ang nagkikita sa isang koridor at parehong tumabi, gumagalaw sa parehong direksyon. Starvation — isang thread ay hindi nakakakuha ng access sa isang mapagkukunan dahil ang ibang mga thread ay patuloy na humaharang dito. Solusyon: patas na mga lock (fair locks), mga priyoridad ng thread nang may pag-iingat.

Mga Tool sa Synchronization

Upang maiwasan ang mga problema sa multithreading, ginagamit ang mga synchronization primitive: Mutex (mutual exclusion), Semaphore (paglilimita sa bilang ng sabay-sabay na pag-access), Lock (interface na may tryLock), Synchronized (lock sa antas ng JVM), @MainActor (Swift — garantiya ng pagpapatupad sa pangunahing thread). Sa Android, available din ang ThreadPool sa pamamagitan ng Executors.newFixedThreadPool, newCachedThreadPool. Gayunpaman, ang manu-manong pamamahala ng pool ay prerogative ng mga legacy na proyekto; sa mga bagong proyekto, mas mahusay na gumamit ng mga coroutine.

Tool Platform Uri Mga Tampok
DispatchQueue (GCD)iOSPila ng gawainSerial/Concurrent, QoS priyoridad, Thread Pool pinamamahalaan ng system
OperationQueueiOSPila ng operasyonMga dependency, pagkansela, maxConcurrentOperationCount
Coroutines + FlowAndroidCoroutineMagaan, structured concurrency, StateFlow, Channel
RxJava / RxKotlinAndroidReactive streamObservable, Schedulers, mayamang hanay ng operator
CombineiOSReactive streamPublisher/Subscriber, integrasyon ng SwiftUI
Async/Await + TaskiOS / AndroidAsynchronous na modeloLinear code, @MainActor, structured concurrency

Mga Madalas Itanong

Ano ang pagkakaiba ng Main Thread at Background Thread?

Ang Main Thread (UI thread) ay responsable para sa pag-render ng interface at pagproseso ng mga pagpindot. Ang Background Thread ay nagsasagawa ng mga gawain sa likuran — pag-load ng data, pagkalkula, trabaho sa network. Ang pag-block sa Main Thread ay nagiging sanhi ng pag-freeze ng interface (ANR sa Android, frozen UI sa iOS).

Ano ang Race Condition at paano ito maiiwasan?

Race Condition — kondisyon ng karera kapag ang dalawang thread ay sabay na pumapasok sa shared data at ang resulta ay depende sa pagkakasunud-sunod ng pagpapatupad. Naiiwasan sa pamamagitan ng synchronization: Mutex, Semaphore, Lock, Synchronized, @MainActor o atomic na operasyon.

Coroutines o RxJava: alin ang pipiliin para sa Android?

Ang Coroutines ay ang modernong pamantayan para sa Android (JetBrains, suportado ng Google). RxJava/RxKotlin ay isang reaktibong diskarte na may mayamang hanay ng operator. Ang Coroutines ay mas simple para sa asynchronous na mga tawag, ang RxJava ay mas malakas para sa kumplikadong mga stream ng data. Sa IT Sectr gumagamit kami ng Coroutines + Flow para sa mga bagong proyekto.

Ano ang Deadlock at Livelock?

Deadlock — isang mutual blocking kung saan ang dalawang thread ay naghihintay para sa mga mapagkukunan ng bawat isa. Livelock — ang mga thread ay hindi naka-block ngunit patuloy na nagpapasa ng mga mapagkukunan nang walang kapaki-pakinabang na trabaho. Ang parehong mga problema ay nalutas sa pamamagitan ng tamang pagkakasunud-sunod ng lock at mga timeout.

Bakit kailangan ang DispatchQueue sa iOS?

Ang DispatchQueue ay isang abstraction ng Grand Central Dispatch (GCD) para sa pamamahala ng thread. Ang Main Queue ay nagsasagawa ng mga gawain sa pangunahing thread, ang Global Queues — sa mga background thread. Ang Serial Queue ay ginagarantiyahan ang sunud-sunod na pagpapatupad, ang Concurrent Queue — parallel. Sa mga modernong proyekto, ang GCD ay kadalasang pinapalitan ng Async/Await at Task.

Buod

  • Main Thread — UI lamang; lahat ng iba pang operasyon sa Background
  • GCD at OperationQueue — ang pundasyon ng multithreading sa iOS; Async/Await — ang modernong alternatibo
  • Coroutines at Flow — ang pamantayan para sa Android; structured concurrency pumipigil sa pagtagas
  • RxJava, RxSwift, Combine — reaktibong framework para sa kumplikadong mga stream ng data
  • Race Condition at Deadlock — ang mga pangunahing problema; nalutas ng mga lock at tamang pagkakasunud-sunod ng pagkuha ng mapagkukunan
  • Thread Pool ay pinamamahalaan ng system (GCD) o framework (coroutine); ang manu-manong paggawa ng thread ay hindi inirerekomenda
  • Ang pagpili ng tool ay depende sa platform: Coroutines para sa Android, GCD/Combine para sa iOS

Gagawa kami ng mobile application na turnkey

Gumagawa ang IT Sectr ng mga iOS at Android application para sa mga startup at negosyo mula noong 2017. Magpapayo kami sa iyo at magmumungkahi ng pinakamahusay na solusyon.

Pag-usapan ang proyekto