Thread Pool — bu, iplik idarəetmə mexanizmidir, burada əvvəlcədən yaradılmış iplik hovuzu tapşırıqları yerinə yetirmək üçün təkrar istifadə olunur, ipliklərin yaradılması və məhv edilməsi xərclərindən qaçınır. Mobil inkişafda iplik hovuzu fon əməliyyatları üçün istifadə olunur: şəbəkə sorğuları, şəkil emalı, verilənlər bazası işləri. Google Android Documentation (2025) məlumatına görə, ExecutorService Android-də fon ipliklərini idarə etmək üçün tövsiyə olunan üsuldur. iOS-da analoji rolu OperationQueue və qlobal eyni vaxtda (concurrent) növbələri olan GCD DispatchQueue oynayır.
Əsas məqamlar
Thread Pool (iplik hovuzu) — bu, sabit sayda ipliklərin əvvəlcədən yaradıldığı və çoxsaylı tapşırıqları yerinə yetirmək üçün təkrar istifadə edildiyi arxitektura nümunəsidir. Hər əməliyyat üçün yeni iplik yaratmaq əvəzinə (bu baha başa gəlir: JVM-də hər iplik üçün təxminən 1 MB yığın), tapşırıqlar növbəyə yerləşdirilir və hovuzdan boş ipliklər tərəfindən yerinə yetirilir. Mobil inkişafda iplik hovuzu performans üçün kritik əhəmiyyət daşıyır — Android və iOS tətbiq üçün iplik sayını məhdudlaşdırır.
İplik yaratmaq bahalı əməliyyatdır: yığın ayırma, sistemdə qeydiyyat, kontekst dəyişmə. Məhdud resurslara malik mobil cihazlarda nəzarətsiz iplik yaratma Android-də OOM (OutOfMemoryError) və iOS-da tənzimləməyə (throttling) səbəb olur. Thread Pool hər iki problemi həll edir: eyni vaxtda işləyən ipliklərin maksimum sayını məhdudlaşdırır və artıq yaradılmış iplikləri təkrar istifadə edir. Google raw Thread() əvəzinə ExecutorService tövsiyə edir, Apple Thread əvəzinə OperationQueue tövsiyə edir.
| Parametr | Hovuzsuz (raw Thread) | Thread Pool ilə |
|---|---|---|
| İplik yaratma | Hər tapşırıq üçün | Hovuz yaradılarkən bir dəfə |
| Maksimum iplik | Məhdudiyyətsiz (OOM riski) | Core/max pool size ilə məhdudlaşdırılıb |
| İstifadə | Aşağı (iplik tapşırıqdan sonra ölür) | Yüksək (iplik təkrar istifadə olunur) |
| İdarəetmə | Əl ilə (join, interrupt) | Avtomatik (ExecutorService) |
| Yaddaş istehlakı | Hər tapşırıqla artır | Sabit |
İplik hovuzu Producer-Consumer prinsipi ilə işləyir: tapşırıqlar (Runnable/Callable) bloklaşdırıcı növbəyə (BlockingQueue) yerləşdirilir. Hovuzdakı ipliklər növbədə tapşırıqları gözləyir və onları yerinə yetirmək üçün götürür. Alqoritm: boş ipliklər corePoolSize-dən azdırsa, yeni iplik yaradılır. corePoolSize-ə çatılıbsa, tapşırıq növbəyə yerləşdirilir. Növbə doludursa və ipliklər maximumPoolSize-dən azdırsa, əlavə iplik yaradılır. maximumPoolSize aşıldıqda tapşırıq RejectedExecutionHandler vasitəsilə rədd edilir.
Core pool size — boş vəziyyətdə belə hovuzda saxlanılan ipliklərin sayı. Maximum pool size — növbə daşdıqda yaradıla biləcək maksimum iplik sayı. Onların arasındakı fərq müvəqqəti yaradılan və boş vaxt aşımından sonra bitən əlavə (overflow) ipliklərdir. Mobil cihazlarda iplik yaratma pik yüklərindən qaçmaq üçün corePoolSize-ün maximumPoolSize-ə bərabər qurulması tövsiyə olunur.
BlockingQueue icra gözləyən tapşırıqları saxlayır. Ən populyar tətbiqlər: LinkedBlockingQueue (məhdudiyyətsiz), ArrayBlockingQueue (məhdud) və SynchronousQueue (saxlamadan — tapşırıq dərhal iplikə ötürülür). Növbə və hovuz daşdıqda RejectedExecutionHandler işə düşür. Standart siyasətlər: AbortPolicy (RejectedExecutionException atır), CallerRunsPolicy (göndərənin iplikdə icra edir), DiscardPolicy və DiscardOldestPolicy.
// Android-də Thread Pool yaratma
val threadPool = ThreadPoolExecutor(
corePoolSize = 2, // Minimum 2 iplik
maximumPoolSize = 4, // Maksimum 4 iplik
keepAliveTime = 30L, // Daşqın iplik ömrü
unit = TimeUnit.SECONDS,
workQueue = LinkedBlockingQueue<Runnable>(16),
threadFactory = Executors.defaultThreadFactory(),
handler = ThreadPoolExecutor.CallerRunsPolicy()
)
// Tapşırıqların göndərilməsi
threadPool.execute {
val result = api.fetchData()
runOnUiThread { showData(result) }
}
// Hovuzun bitirilməsi
threadPool.shutdown()
// Bütün tapşırıqların bitməsinin gözlənilməsi
threadPool.awaitTermination(10, TimeUnit.SECONDS)
Android java.util.concurrent vasitəsilə iplik hovuzunun bir neçə tətbiqini təqdim edir. Executors — hazır konfiqurasiyaları olan fabrik: newFixedThreadPool(n) (sabit hovuz), newCachedThreadPool() (məhdudiyyətsiz, lazım olduqda ipliklər yaradılır), newSingleThreadExecutor() (bir iplik — ardıcıl icra). Mobil layihələr üçün ağlabatan limitlə (2-4 iplik) newFixedThreadPool tövsiyə olunur, çünki cached hovuz çox sayda iplik yarada bilər.
ThreadPoolExecutor (TPE) — konfiqurasiya edilə bilən parametrləri olan ExecutorService-in tam tətbiqi. Android-də TPE AsyncTask, IntentService və JobIntentService daxilində istifadə olunur. corePoolSize, maximumPoolSize, keepAliveTime, BlockingQueue və RejectedExecutionHandler parametrləri hovuz davranışını incə tənzimləməyə imkan verir. Android üçün tövsiyələr: corePoolSize = CPU nüvələrinin sayı - 1 (IO-bound tapşırıqlar üçün) və ya nüvələrin sayı (CPU-bound tapşırıqlar üçün). Tipik tətbiqlər üçün — 2-4 iplik.
// Executors hazır konfiqurasiyaları
// 1. 3 iplik üçün sabit hovuz
val fixedPool = Executors.newFixedThreadPool(3)
// 2. Keşlənmiş hovuz (mobil üçün tövsiyə edilmir)
val cachedPool = Executors.newCachedThreadPool()
// 3. Tək iplik (seriyalaşdırma)
val singlePool = Executors.newSingleThreadExecutor()
// 4. Planlayıcı (dövri tapşırıqlar)
val scheduler = Executors.newScheduledThreadPool(2)
// Callable və Future ilə istifadə
val future: Future<String> = fixedPool.submit(Callable {
"Result: ${api.call()}"
})
// Nəticənin alınması (iplik bloklayır)
val result = future.get(5, TimeUnit.SECONDS)
// Hovuzun bitirilməsi
fixedPool.shutdownNow()
Kotlin korutinləri CoroutineDispatcher təqdim edir — iplik hovuzuna bənzər abstraksiya. Dispatchers.IO 64 iplikdən ibarət hovuzdan (məhdud) istifadə edir. Dispatchers.Default — CPU nüvələrinin sayına bərabər hovuz. CoroutineDispatcher əl ilə shutdown tələb etmir və avtomatik idarə olunur. İncə tənzimləmə üçün Executors.newFixedThreadPool(2).asCoroutineDispatcher() vasitəsilə öz ExecutorCoroutineDispatcher-inizi yaradın. Korutinlər iplik hovuzunu əvəz etmir, onu bükür.
iOS iplik hovuzunu idarə etmək üçün iki əsas mexanizm təqdim edir: OperationQueue (GCD əsasında yüksək səviyyəli API) və GCD DispatchQueue (aşağı səviyyəli C-API). OperationQueue iplik hovuzunu maxConcurrentOperationCount xüsusiyyəti vasitəsilə inkapsulə edir. Standart olaraq OperationQueue system-defined maximum-dan istifadə edir (sistem yükündən asılıdır). DispatchQueue.global() sistem iplik hovuzu ilə eyni vaxtda (concurrent) növbə təqdim edir.
OperationQueue iplik hovuzunu maxConcurrentOperationCount vasitəsilə idarə edir. Qiymət 1 serial növbə yaradır (tək iplik hovuzuna bənzər). Qiymət 1-dən böyük — göstərilən limitlə eyni vaxtda hovuz. Standart olaraq maxConcurrentOperationCount = NSOperationQueueDefaultMaxConcurrentOperationCount (sistem optimumu, adətən 4-8 iplik). Operation asılılıqları, prioritetləri və ləğvi dəstəkləyir. Hər bir əməliyyat sistem hovuzundan istənilən boş iplikdə yerinə yetirilir.
// 3 iplik hovuzu ilə OperationQueue
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 3
queue.qualityOfService = .utility
// Əməliyyatların yaradılması
let operation1 = BlockOperation {
let data = fetchData(from: url1)
DispatchQueue.main.async { updateUI(data) }
}
let operation2 = BlockOperation {
let data = fetchData(from: url2)
DispatchQueue.main.async { updateUI(data) }
}
// Asılılıq: operation2 operation1-i gözləyir
operation2.addDependency(operation1)
// Növbəyə əlavə etmə
queue.addOperations([operation1, operation2], waitUntilFinished: false)
// Bütün əməliyyatların ləğvi
queue.cancelAllOperations()
DispatchQueue — Apple-dan iplik hovuzudur. Eyni vaxtda növbə (qos: .utility) cihazın cari yükünə uyğun optimallaşdırılmış sistem iplik hovuzundan istifadə edir. Müxtəlif QoS (userInteractive, userInitiated, utility, background) müxtəlif prioritetlərlə müxtəlif hovuzlara xəritələnir. DispatchGroup bir neçə tapşırığı sinxronlaşdırmağa imkan verir. DispatchWorkItem ləğv və qualityOfService-i dəstəkləyir. İncə nəzarət üçün DispatchQueue(label: qos: attributes: .concurrent) vasitəsilə öz eyni vaxtda növbələrinizi yaradın.
// GCD DispatchQueue iplik hovuzu kimi
let customQueue = DispatchQueue(
label: "com.app.background",
qos: .utility,
attributes: .concurrent,
autoreleaseFrequency: .workItem
)
// Tapşırıqların hovuza göndərilməsi
customQueue.async { self.processFile(file1) }
customQueue.async { self.processFile(file2) }
// Sinxronizasiya üçün DispatchGroup
let group = DispatchGroup()
let pool = DispatchQueue.global(qos: .utility)
pool.async(group: group) { fetchData() }
pool.async(group: group) { processImage() }
group.notify(queue: .main) {
self.showResult() // Hər iki tapşırıq bitdi
}
// Semaphore vasitəsilə eyni vaxtda işləmənin məhdudlaşdırılması
let semaphore = DispatchSemaphore(value: 3)
for url in urls {
pool.async {
semaphore.wait()
download(url)
semaphore.signal()
}
}
İplik hovuzunun konfiqurasiyası birbaşa tətbiqin performansına təsir edir. Yanlış parametrlər CPU-nun tam istifadə edilməməsinə (çox az iplik) və ya sistemin həddindən artıq yüklənməsinə (çox çox iplik) səbəb olur. Mobil tətbiqlər üçün optimal dəyərlər məhdud resurslar və enerji istehlakı səbəbindən server dəyərlərindən fərqlənir. Əsas parametrlər: corePoolSize, maxPoolSize, queue capacity və keepAliveTime.
IO-bound tapşırıqlar üçün düstur: corePoolSize = CPU nüvələrinin sayı × 2 (ipliklər giriş-çıxış gözləyir). CPU-bound tapşırıqlar üçün: corePoolSize = CPU nüvələrinin sayı (ipliklər daim hesablamalarla məşğuldur). Müasir mobil cihazlarda (6-8 nüvə) bu, CPU-bound üçün 6-8 iplik, IO-bound üçün 12-16 iplik verir. Praktiki testlər göstərir ki, tipik mobil tətbiq üçün 3-4 iplik optimaldır — daha çox iplik enerji istehlakını artırır, performans artımı vermir.
Tapşırıq növbəsinin (work queue) ölçüsü neçə tapşırığın icra gözləyə biləcəyini müəyyən edir. Məhdudiyyətsiz növbə (limitsiz LinkedBlockingQueue) sürətli tapşırıq axınında OOM-a səbəb ola bilər. Məhdud növbə (sabit ölçülü ArrayBlockingQueue) daşdıqda tapşırıqları rədd edir. Mobil tətbiqlər üçün 16-32 tapşırıq tutumu olan ArrayBlockingQueue tövsiyə olunur. CallerRunsPolicy mobil qurğular üçün ən yaxşı RejectedExecutionHandler-dir: tapşırığı itirmək əvəzinə göndərəni yavaşladır (pressure back).
| Parametr | Mobil üçün tövsiyə | Əsaslandırma |
|---|---|---|
| corePoolSize | 2-4 | Mobil cihazın məhdud resursları |
| maxPoolSize | corePoolSize (və ya +1-2) | İplik yaratma pik yüklərindən qaçınma |
| keepAliveTime | 15-30 saniyə | Sürətli yaddaş boşaltma, lakin tez-tez yaratma olmadan |
| Queue capacity | 16-32 | Buferləşdirmə və OOM riski arasında tarazlıq |
| Handler | CallerRunsPolicy | Tapşırıq itkisi olmadan geri təzyiq (backpressure) |
Mobil tətbiq tərtibatçıları iplik hovuzundan istifadə edərkən tez-tez səhvlərə yol verirlər ki, bu da çökmələrə, yaddaş sızmalarına və qeyri-sabit işə səbəb olur. Ən çox rast gəlinənlər: ExecutorService üçün shutdown() çağırmamaq, hər əməliyyat üçün yeni hovuz yaratmaq, çox böyük hovuz, tapşırıqlar arasında deadlock, Android-də CachedThreadPool istifadə etmək.
Deadlock o zaman yaranır ki, hovuzdakı bir tapşırıq eyni hovuzdan başqa bir tapşırığın nəticəsini gözləyir, lakin bütün ipliklər gözləmə ilə məşğuldur. Nümunə: A tapşırığı B tapşırığını eyni hovuza göndərir və future.get() çağırır — hovuz tükənibsə, A tapşırığı B tapşırığını gözləyir, B tapşırığı isə icra oluna bilmir, çünki boş iplik yoxdur. Həll yolu: müxtəlif səviyyəli tapşırıqlar üçün ayrı hovuzlar və ya bloklaşdırıcı .get() əvəzinə async callback istifadə edin.
// Thread Pool-da Deadlock
val pool = Executors.newFixedThreadPool(1)
// A tapşırığı B tapşırığını gözləyir — deadlock!
val futureA = pool.submit {
// Bu tapşırıq heç vaxt yerinə yetirilməyəcək
val futureB = pool.submit { 42 }
futureB.get() // Əbədi bloklanır
}
// Düzəliş: ayrı hovuzlar
val workerPool = Executors.newFixedThreadPool(2)
val callbackPool = Executors.newSingleThreadExecutor()
workerPool.submit {
callbackPool.submit {
// Ayrı hovuzda icra olunur — deadlock mümkün deyil
}
}
// Və ya CompletableFuture istifadə edin
workerPool.submit {
CompletableFuture
.supplyAsync { 42 }
.thenAccept { result ->
println(result)
}
}
Activity-də yaradılmış ExecutorService onDestroy()-də bitirilməlidir. Əgər bu edilməzsə, ipliklər Activity məhv edildikdən sonra da yaddaşda qalır. Həll yolu: hovuzu Activity əvəzinə Application scope və ya ViewModel-də saxlayın. Korutinlər üçün viewModelScope və ya lifecycleScope istifadə edin. Hovuz Activity daxilində yaradılıbsa, onDestroy()-də mütləq pool.shutdown() çağırın. Test üçün dərhal dayandırmaq üçün es.shutdownNow() istifadə edin.
Tez-tez verilən suallar
Thread Pool artıq yaradılmış iplikləri çoxsaylı tapşırıqları yerinə yetirmək üçün təkrar istifadə edir. Adi iplik (raw Thread) yaradılır, bir tapşırığı yerinə yetirir və məhv edilir. İplik yaratmaq təxminən 1 MB yaddaş və ~1 ms vaxt aparır. Thread Pool xərcləri azaldır, maksimum iplik sayını məhdudlaşdırır və idarəetmə API-si (shutdown, awaitTermination) təqdim edir.
Tipik mobil tətbiq üçün optimal 2-4 iplik-dir. CPU-bound tapşırıqlar üçün — CPU nüvələrinin sayı. IO-bound tapşırıqlar üçün — nüvələrin sayı × 2. Daha çox iplik enerji istehlakını və kontekst dəyişməsini artırır, performans artımı vermir. Android-də nüvələri təyin etmək üçün Process.availableProcessors() istifadə edin. iOS-da — ProcessInfo.processInfo.processorCount.
CachedThreadPool ehtiyaca uyğun ipliklər yaradır və mövcud olanları təkrar istifadə edir. Problem: maksimum iplik sayını məhdudlaşdırmır. Əgər 100 tapşırıq eyni anda gələrsə, 100 iplik yaradılacaq. Bu, Android-də OOM-a səbəb olur (hər iplik ~1 MB). Aydın limitlə newFixedThreadPool(n) istifadə edin. CachedThreadPool yalnız kiçik həcmə zəmanət verilən qısamüddətli partlayış (burst) tapşırıqları üçün icazəlidir.
Bəli, hovuz idarə olunan konteynerə (korutinlər kimi) aid deyilsə. shutdown() yeni tapşırıqların qəbulunu dayandırır və cari tapşırıqlar bitdikdən sonra iplikləri sonlandırır. shutdown() olmadan ipliklər yaddaşda qalır, tətbiq bitmir. Activity üçün onDestroy()-də çağırın. ViewModel üçün coroutineScope istifadə edin. Hovuzun bitirilməsi resurs idarəetməsinin məcburi hissəsidir, Cursor və ya InputStream-in bağlanmasına bənzər.
Bəli, OperationQueue və DispatchQueue iOS tərəfindən təqdim edilən iplik hovuzudur. OperationQueue maxConcurrentOperationCount vasitəsilə eyni vaxtda işləməni məhdudlaşdırır. DispatchQueue.global() birbaşa nəzarət olmadan sistem iplik hovuzundan istifadə edir. Java ThreadPoolExecutor-dən fərqli olaraq, siz corePoolSize və ya queue capacity-ni idarə etmirsiniz — sistem hovuzu cari yük və cihazın enerji istehlakına uyğun avtomatik optimallaşdırır.
Xülasə
Açar təslim mobil tətbiq hazırlayacağıq
IT Sectr 2017-ci ildən startaplar və bizneslər üçün iOS və Android tətbiqləri yaradır. Sizə məsləhət verəcəyik və ən yaxşı həlli təklif edəcəyik.
Həm də oxuyun