Her mobil uygulama aynı anda birçok görevi yerine getirir: ağdan veri yüklemek, kullanıcı dokunuşlarını işlemek, arayüzü canlandırmak ve dosyaları kaydetmek. Tüm bu kod tek bir iş parçacığında çalışırsa, herhangi bir ağ gecikmesinde uygulama donar. Çoklu iş parçacığı (multithreading) ve eşzamanlılık, uygulamanın duyarlı ve verimli kalmasını sağlayan temel kavramlardır. Bu makalede tüm ana araçları inceleyeceğiz: Main Thread ve RunLoop'dan Kotlin coroutine'lerine ve iOS'ta Combine'a kadar. İçerik Apple'ın resmi GCD belgelerine dayanmaktadır.
Önemli Noktalar
Çoklu iş parçacığı, bir uygulamanın aynı anda birden fazla kod parçasını yürütebilme yeteneğidir. Her parça, kendi çağrı yığınına sahip hafif bir süreç olan ayrı bir iş parçacığında (Thread) çalışır. Mobil geliştirmede, iş parçacıkları iki kategoriye ayrılır: Main Thread (UI iş parçacığı) ve Background Threads (arka plan iş parçacıkları).
İşletim sistemi, iş parçacıklarının işlemci çekirdekleri arasında dağıtımını kendisi yönetir. Modern cihazlarda 6–8 çekirdek bulunur, bu nedenle paralel yürütme işi hızlandırabilir. Ancak, iş parçacığı oluşturmak pahalı bir işlemdir, bu nedenle doğrudan Thread ile çalışmak önerilmez. Bunun yerine, daha yüksek seviyeli soyutlamalar kullanılır: DispatchQueue, OperationQueue, CoroutineDispatcher.
Eşzamanlılık (Concurrency), çoklu iş parçacığından daha geniş bir kavramdır. Eşzamanlılık, görevlerin bağlam değiştirme yoluyla tek bir çekirdekte bile "aynı anda" yürütülebileceği anlamına gelir. Asenkronizasyon (Async/Await), bir görevin iş parçacığını bloke etmediği, sonucu beklerken kontrolü geri verdiği bir programlama modelidir. Modern diller (Kotlin, Swift, Dart) yerleşik Async/Await desteğine sahiptir.
IT Sectr'de, projenin başlangıcında doğru çoklu iş parçacığı mimarisine özel önem veriyoruz. Erken aşamada yapılan hatalar, yakalanması zor hatalara yol açar: veri yarışları, kilitlenmeler ve yük altında uygulama dengesizliği. Projelerimizin her biri, planlama aşamasında eşzamanlılık mimarisi incelemesinden geçer.
Main Thread (ana iş parçacığı) — bir mobil uygulamada UI'ya erişimi olan tek iş parçacığıdır. Android'de buna UI Thread denir, iOS'ta — Main Thread. Tüm arayüz işlemleri — metin değiştirme, animasyonlar, dokunuş işleme — yalnızca Main Thread'de gerçekleştirilir. Ana iş parçacığında ağır bir işlem (dosya yükleme, JSON ayrıştırma) gerçekleştirilirse, arayüz yanıt vermeyi durdurur. Android'de bu, ANR'ye (Application Not Responding) yol açar, iOS'ta — ekranın "donmasına".
Background Threads (arka plan iş parçacıkları), UI ile ilgili olmayan her şey için tasarlanmıştır: ağ istekleri, veritabanı işlemleri, görüntü işleme, şifreleme. Tamamlandıktan sonra, sonuç görüntülenmek üzere Main Thread'e aktarılır. Her platform, iş parçacıkları arasında geçiş yapmak için kendi araçlarını sağlar: iOS'ta DispatchQueue.main.async, Android'de runOnUiThread veya withContext(Dispatchers.Main).
RunLoop — iOS ana iş parçacığındaki olay işleme döngüsüdür. RunLoop olayları (dokunuşlar, zamanlayıcılar, bildirimler) bekler ve bunları uygun işleyicilere dağıtır. Android'de karşılığı, her Main Thread ile ilişkili olan Looper'dır. Main Looper, kuyruktan sonsuz bir şekilde mesaj çıkarır ve işlenmek üzere Handler'a iletir. RunLoop ve Looper'ı anlamak, bellek sızıntılarını ve arayüz "takılmalarını" önlemeye yardımcı olur.
Grand Central Dispatch (GCD) — C dili seviyesinde çoklu iş parçacığını yönetmek için Apple kütüphanesidir. GCD, DispatchQueue — görev kuyrukları ile çalışır. Geliştirici manuel olarak iş parçacığı oluşturmaz; GCD, bir iş parçacığı havuzunu (Thread Pool) yöneterek görevleri mevcut işlemci çekirdeklerine dağıtır. DispatchQueue iki türdür: Serial Queue (seri kuyruk — görevler birbiri ardına yürütülür) ve Concurrent Queue (eşzamanlı kuyruk — görevler aynı anda yürütülebilir).
Main DispatchQueue — ana iş parçacığına bağlı seri bir kuyruktur. Global Queues — farklı önceliklere (QoS — Quality of Service) sahip eşzamanlı kuyruklardır: userInteractive, userInitiated, utility, background. Doğru QoS'yi seçmek performans için kritiktir: .userInteractive — UI'yı etkileyen görevler için (animasyonlar, oluşturma); .background — zaman açısından kritik olmayan görevler için (senkronizasyon, önbellek temizleme).
OperationQueue — ek yeteneklere sahip GCD üzerinde bir soyutlamadır: görevleri iptal etme, işlemler arasında bağımlılıklar ayarlama, eşzamanlı işlemlerin maksimum sayısını kontrol etme. İşlemler, Operation sınıfının (veya BlockOperation) nesneleridir. Örnek: bir görüntü yüklemeniz, ardından bir filtre uygulamanız ve ancak ondan sonra görüntülemeniz gerekiyorsa — bağımlılıkları olan OperationQueue bunu mükemmel şekilde halleder. GCD'de bu adımları DispatchGroup veya semafor kullanarak manuel olarak senkronize etmeniz gerekirdi.
Swift 5.5+'da Async/Await — GCD'ye modern bir alternatiftir. async ve await anahtar kelimeleri, asenkron kodu doğrusal ve okunabilir hale getirir. İşlevler async olarak işaretlenir ve çağrılar await ile beklenir. Sistem, bağlam değiştirmeyi kendisi yönetir: varsayılan olarak, bir async işlevi arka plan iş parçacığında yürütülürken, UI güncellemesi MainActor'da yürütülür. @MainActor — kodun ana iş parçacığında yürütülmesini garanti eden bir niteliktir.
Coroutines (coroutine'ler) — JetBrains tarafından geliştirilen Kotlin için hafif iş parçacıklarıdır. Normal iş parçacıklarının aksine, coroutine'ler belirli bir Thread'e bağlı değildir. Binlerce coroutine, önemli bir ek yük olmadan birden çok iş parçacığında çalışabilir. CoroutineScope, coroutine'lerin yaşam döngüsünü yönetir: viewModelScope, ViewModel'e bağlıdır, lifecycleScope — Activity/Fragment'e. Kapsam yok edildiğinde, tüm alt coroutine'ler otomatik olarak iptal edilir.
Dispatchers, coroutine'in hangi iş parçacığı havuzunda çalışacağını belirler: Dispatchers.Main — UI iş parçacığı; Dispatchers.IO — ağ istekleri ve disk işlemleri için; Dispatchers.Default — CPU yoğun hesaplamalar için. Dağıtıcıyı değiştirmek için withContext kullanılır. Coroutine'ler yapılandırılmış eşzamanlılığı (structured concurrency) destekler: her coroutine'in bir ebeveyni vardır ve ebeveyn iptal edildiğinde tüm alt coroutine'ler iptal edilir. Bu, bellek sızıntılarını ve asılı görevleri önler.
Flow — coroutine kütüphanesinden soğuk asenkron veri akışıdır. Flow, değerleri sırayla yayar: (1) üretici veri üretir, (2) operatörler akışı dönüştürür, (3) toplayıcı sonucu tüketir. LiveData'dan farklı olarak Flow, karmaşık operatör zincirlerini (map, filter, flatMapConcat, catch) destekler ve tamamen iş parçacığı güvenlidir. StateFlow ve SharedFlow — Flow'un sıcak varyantlarıdır, UI durumu ve tek seferlik olaylar (Snackbar, navigasyon) için idealdir.
Channel — coroutine'ler arasında veri iletmek için başka bir coroutine soyutlamasıdır. Channel bir kuyruk gibi çalışır: bir gönderen (send) ve bir veya daha fazla alıcı (receive). Arabellekli kanallar (Channel(UNLIMITED), Channel(BUFFERED)), taşma durumunda davranışı yapılandırmaya olanak tanır. Channel, geri arama tabanlı API'leri coroutine'lere köprülemek için Flow ile birlikte sıkça kullanılır: callbackFlow { … }.
IT Sectr'de tüm Android projelerinde aktif olarak coroutine ve Flow kullanıyoruz. Bu, senkron görünen asenkron kod yazmayı sağlar, test etmesi kolaydır (runTest, TestDispatcher) ve manuel iş parçacığı yönetimi gerektirmez. Veri yüklemeli basit bir coroutine örneği:
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()
}
}
}
Reaktif programlama — verilerin asenkron akışlar (Observable, Publisher) olarak yayıldığı bir paradigmadır. RxJava/RxKotlin — Android için en popüler uygulamadır, .NET Rx'ten taşınmıştır. RxSwift — iOS için benzer bir kütüphanedir. Ana bileşenler: Observable (olay kaynağı), Observer (abone), Scheduler (iş parçacığı yönetimi), Operators (akış dönüşümü).
Combine — iOS 13'te tanıtılan, Apple'ın reaktif programlama framework'üdür. Combine, Publisher (yayıncı) ve Subscriber (abone) protokollerini kullanır. RxSwift'ten farklı olarak Combine, SDK'ya entegredir ve SwiftUI ile yakından tümleşiktir. Combine'daki operatörler: map, filter, combineLatest, zip, debounce, throttle — veri bağlamadan UI'a ve arama sorgusu debounce'una kadar çoğu senaryoyu kapsar.
Future ve Promise — tek bir asenkron sonuçla çalışmak için kalıplardır. Future, daha sonra kullanılabilecek bir değeri temsil eder. Promise, bir değer sağlama sözüdür. Rx'te bu Single (bir başarılı yanıt veya hata), Combine'da — Future Publisher'dır. Pratikte, Future/Promise tek API istekleri için uygundur, Observable/Publisher ise sürekli akışlar (coğrafi konum, metin girişi) için uygundur.
Callback ve Delegate — asenkron işlemler için klasik kalıplardır. Callback — bir argüman olarak iletilen ve işlem tamamlandığında çağrılan bir işlevdir. Delegate — olay işleyici yöntemlerine sahip bir protokolü uygulayan bir nesnedir. Dezavantajı: "callback cehennemi" (iç içe callback'ler) ve hata işleme karmaşıklığı. NotificationCenter (iOS) ve EventBus (Android) — gevşek bağlı iletişim için kullanışlı ancak örtük bağımlılıklara yol açan yayın olay mekanizmalarıdır.
Çoklu iş parçacığı, yüksek performansın kapısını açar ancak aynı zamanda yakalanması zor hatalar riski yaratır. En yaygın olanları: Race Condition (yarış durumu), Deadlock (kilitlenme), Livelock (canlı kilitlenme) ve Starvation (açlık). Bu sorunları anlamak, herhangi bir mobil geliştirici için zorunlu bir beceridir.
Race Condition, iki veya daha fazla iş parçacığının senkronizasyon olmadan aynı verileri aynı anda okuması ve yazmasıyla ortaya çıkar. Sonuç, hangi iş parçacığının önce yürütüldüğüne bağlıdır. Klasik örnek: iki iş parçacığı bir sayacı artırır. "Oku → artır → yaz" işlemi atomik değildir, bu nedenle aynı anda yürütüldüğünde bir artış "kaybolur". Çözüm — atomik işlemler (AtomicInteger, AtomicReference) veya kilitler (Mutex, Semaphore, synchronized) kullanmaktır.
Deadlock — her iş parçacığının bir kaynağı tuttuğu ve başka bir iş parçacığı tarafından tutulan bir kaynağı beklediği durumdur. Hiçbir iş parçacığı devam edemez. Oluşma koşulları: karşılıklı dışlama, tut ve bekle, önleme yok, döngüsel bekleme. Önleme: tek bir kilit edinme sırası oluşturma, zaman aşımlı tryLock kullanma, Lock-Free algoritmaları (ConcurrentHashMap, CopyOnWriteArrayList) uygulama.
Livelock — iş parçacıkları bloke değildir ancak yararlı iş yapmadan sürekli olarak birbirlerine kaynakları "aktarır". Örnek: iki kişi bir koridorda karşılaşır ve ikisi de aynı yönde hareket ederek kenara çekilir. Starvation — bir iş parçacığı, diğer iş parçacıkları sürekli olarak onu engellediği için bir kaynağa erişemez. Çözüm: adil kilitler (fair locks), dikkatli iş parçacığı öncelikleri.
Çoklu iş parçacığı sorunlarını önlemek için senkronizasyon ilkelleri kullanılır: Mutex (karşılıklı dışlama), Semaphore (eşzamanlı erişim sayısını sınırlama), Lock (tryLock ile arayüz), Synchronized (JVM düzeyinde kilit), @MainActor (Swift — ana iş parçacığında yürütmeyi garanti eder). Android'de ayrıca Executors.newFixedThreadPool, newCachedThreadPool aracılığıyla ThreadPool (iş parçacığı havuzu) da mevcuttur. Ancak, manuel havuz yönetimi eski projelerin ayrıcalığıdır; yeni projelerde coroutine kullanmak daha iyidir.
| Araç | Platform | Tür | Özellikler |
|---|---|---|---|
| DispatchQueue (GCD) | iOS | Görev kuyruğu | Seri/Eşzamanlı, QoS öncelikleri, Thread Pool sistem tarafından yönetilir |
| OperationQueue | iOS | İşlem kuyruğu | Bağımlılıklar, iptal, maxConcurrentOperationCount |
| Coroutines + Flow | Android | Coroutine | Hafif, yapılandırılmış eşzamanlılık, StateFlow, Channel |
| RxJava / RxKotlin | Android | Reaktif akış | Observable, Schedulers, zengin operatör seti |
| Combine | iOS | Reaktif akış | Publisher/Subscriber, SwiftUI entegrasyonu |
| Async/Await + Task | iOS / Android | Asenkron model | Doğrusal kod, @MainActor, yapılandırılmış eşzamanlılık |
Sıkça Sorulan Sorular
Main Thread (UI iş parçacığı) arayüzün oluşturulmasından ve dokunuşların işlenmesinden sorumludur. Background Thread arka plan görevlerini gerçekleştirir — veri yükleme, hesaplamalar, ağ çalışması. Main Thread'i bloke etmek arayüz donmasına neden olur (Android'de ANR, iOS'ta frozen UI).
Race Condition — iki iş parçacığının aynı anda paylaşılan verilere eriştiği ve sonucun yürütme sırasına bağlı olduğu yarış durumudur. Senkronizasyon ile önlenir: Mutex, Semaphore, Lock, Synchronized, @MainActor veya atomik işlemler.
Coroutines, Android için modern standarttır (JetBrains, Google tarafından desteklenir). RxJava/RxKotlin, zengin operatör setine sahip reaktif bir yaklaşımdır. Coroutines asenkron çağrılar için daha basittir, RxJava karmaşık veri akışları için daha güçlüdür. IT Sectr'de yeni projeler için Coroutines + Flow kullanıyoruz.
Deadlock — iki iş parçacığının birbirinin kaynaklarını beklediği karşılıklı kilitlenmedir. Livelock — iş parçacıkları bloke değildir ancak yararlı iş yapmadan sürekli kaynakları aktarır. Her iki sorun da uygun kilit sırası ve zaman aşımları ile çözülür.
DispatchQueue, iş parçacığı yönetimi için Grand Central Dispatch'in (GCD) bir soyutlamasıdır. Main Queue, görevleri ana iş parçacığında yürütür, Global Queues — arka plan iş parçacıklarında. Serial Queue sıralı yürütmeyi garanti eder, Concurrent Queue — paralel. Modern projelerde GCD genellikle Async/Await ve Task ile değiştirilir.
Özet
Anahtar teslim bir mobil uygulama geliştireceğiz
IT Sectr, 2017'den beri girişimler ve işletmeler için iOS ve Android uygulamaları oluşturmaktadır. Size danışmanlık yapacak ve en iyi çözümü önereceğiz.