Thread Pool — این یک مکانیزم مدیریت نخها است که در آن یک استخر نخهای از پیش ایجاد شده برای اجرای وظایف مجدداً استفاده میشود و از هزینههای سربار ایجاد و نابودسازی نخها جلوگیری میکند. در توسعه موبایل از استخر نخها برای عملیاتهای پسزمینه استفاده میشود: درخواستهای شبکه، پردازش تصاویر، کار با پایگاههای داده. بر اساس مستندات Google Android (2025)، ExecutorService روش توصیهشده برای مدیریت نخهای پسزمینه در Android است. در iOS نقش مشابهی را OperationQueue و GCD DispatchQueue با صفهای همزمان سراسری ایفا میکنند.
نکات اصلی
Thread Pool (استخر نخها) — یک الگوی معماری است که در آن تعداد ثابتی نخ از پیش ایجاد شده و برای اجرای وظایف متعدد مجدداً استفاده میشود. به جای ایجاد یک نخ جدید برای هر عملیات (که هزینهبر است: حدود ۱ مگابایت پشته برای هر نخ در JVM)، وظایف در یک صف قرار میگیرند و توسط نخهای آزاد از استخر اجرا میشوند. در توسعه موبایل، استخر نخها برای عملکرد حیاتی است — Android و iOS تعداد نخها را در هر برنامه محدود میکنند.
ایجاد نخ یک عملیات پرهزینه است: تخصیص پشته، ثبت در سیستم، تغییر زمینه. در دستگاههای موبایل با منابع محدود، ایجاد کنترلنشده نخها منجر به OOM (OutOfMemoryError) در Android و محدودسازی (throttling) در iOS میشود. Thread Pool هر دو مشکل را حل میکند: حداکثر تعداد نخهای همزمان را محدود میکند و نخهای ایجاد شده را مجدداً استفاده میکند. Google ExecutorService را به جای raw Thread() توصیه میکند، Apple OperationQueue را به جای Thread توصیه میکند.
| پارامتر | بدون استخر (raw Thread) | با Thread Pool |
|---|---|---|
| ایجاد نخ | برای هر وظیفه | یک بار هنگام ایجاد استخر |
| حداکثر نخها | نامحدود (ریسک OOM) | محدود شده توسط core/max pool size |
| استفاده | پایین (نخ پس از وظیفه از بین میرود) | بالا (نخ مجدداً استفاده میشود) |
| مدیریت | دستی (join, interrupt) | خودکار (ExecutorService) |
| مصرف حافظه | با هر وظیفه افزایش مییابد | ثابت |
استخر نخها بر اساس اصل Producer-Consumer کار میکند: وظایف (Runnable/Callable) در یک صف مسدودکننده (BlockingQueue) قرار میگیرند. نخهای موجود در استخر منتظر وظایف در صف میمانند و آنها را برای اجرا برمیدارند. الگوریتم: اگر نخهای آزاد کمتر از corePoolSize باشد، یک نخ جدید ایجاد میشود. اگر corePoolSize رسیده باشد، وظیفه در صف قرار میگیرد. اگر صف پر باشد و نخها کمتر از maximumPoolSize باشد، یک نخ اضافی ایجاد میشود. در صورت تجاوز از maximumPoolSize، وظیفه از طریق RejectedExecutionHandler رد میشود.
Core pool size — تعداد نخهایی که حتی در حالت بیکار در استخر نگه داشته میشوند. Maximum pool size — حداکثر تعداد نخهایی که میتوانند هنگام پر شدن صف ایجاد شوند. تفاوت بین آنها نخهای اضافی (overflow) هستند که به طور موقت ایجاد میشوند و پس از زمان بیکاری پایان مییابند. در دستگاههای موبایل توصیه میشود corePoolSize برابر maximumPoolSize تنظیم شود تا از بارهای اوج ایجاد نخ جلوگیری شود.
BlockingQueue وظایف منتظر اجرا را ذخیره میکند. محبوبترین پیادهسازیها: LinkedBlockingQueue (نامحدود)، ArrayBlockingQueue (محدود) و SynchronousQueue (بدون ذخیرهسازی — وظیفه بلافاصله به نخ منتقل میشود). هنگام پر شدن صف و استخر، RejectedExecutionHandler فعال میشود. سیاستهای استاندارد: AbortPolicy (پرتاب RejectedExecutionException)، CallerRunsPolicy (اجرا در نخ فرستنده)، DiscardPolicy و DiscardOldestPolicy.
// ایجاد Thread Pool در Android
val threadPool = ThreadPoolExecutor(
corePoolSize = 2, // حداقل ۲ نخ
maximumPoolSize = 4, // حداکثر ۴ نخ
keepAliveTime = 30L, // طول عمر نخ اضافی
unit = TimeUnit.SECONDS,
workQueue = LinkedBlockingQueue<Runnable>(16),
threadFactory = Executors.defaultThreadFactory(),
handler = ThreadPoolExecutor.CallerRunsPolicy()
)
// ارسال وظایف
threadPool.execute {
val result = api.fetchData()
runOnUiThread { showData(result) }
}
// پایان استخر
threadPool.shutdown()
// انتظار برای اتمام همه وظایف
threadPool.awaitTermination(10, TimeUnit.SECONDS)
Android چندین پیادهسازی از استخر نخها را از طریق java.util.concurrent ارائه میدهد. Executors — کارخانهای با پیکربندیهای آماده: newFixedThreadPool(n) (استخر ثابت)، newCachedThreadPool() (نامحدود، نخها در صورت نیاز ایجاد میشوند)، newSingleThreadExecutor() (یک نخ — اجرای ترتیبی). برای پروژههای موبایل، newFixedThreadPool با محدودیت معقول (۲-۴ نخ) توصیه میشود، زیرا cached pool میتواند نخهای بیش از حد ایجاد کند.
ThreadPoolExecutor (TPE) — پیادهسازی کامل ExecutorService با پارامترهای قابل تنظیم. در Android، TPE در داخل AsyncTask، IntentService و JobIntentService استفاده میشود. پارامترهای corePoolSize، maximumPoolSize، keepAliveTime، BlockingQueue و RejectedExecutionHandler امکان تنظیم دقیق رفتار استخر را فراهم میکنند. توصیهها برای Android: corePoolSize = تعداد هستههای CPU - ۱ (برای وظایف IO-bound) یا تعداد هستهها (برای وظایف CPU-bound). برای برنامههای معمولی — ۲-۴ نخ.
// پیکربندیهای آماده Executors
// ۱. استخر ثابت برای ۳ نخ
val fixedPool = Executors.newFixedThreadPool(3)
// ۲. استخر کششده (برای موبایل توصیه نمیشود)
val cachedPool = Executors.newCachedThreadPool()
// ۳. نخ تکی (سریالسازی)
val singlePool = Executors.newSingleThreadExecutor()
// ۴. زمانبند (وظایف دورهای)
val scheduler = Executors.newScheduledThreadPool(2)
// استفاده با Callable و Future
val future: Future<String> = fixedPool.submit(Callable {
"Result: ${api.call()}"
})
// دریافت نتیجه (نخ را مسدود میکند)
val result = future.get(5, TimeUnit.SECONDS)
// پایان استخر
fixedPool.shutdownNow()
کوروتینهای کاتلین CoroutineDispatcher را ارائه میدهند — انتزاعی مشابه استخر نخها. Dispatchers.IO از استخر ۶۴ نخ (محدود) استفاده میکند. Dispatchers.Default — استخری برابر با تعداد هستههای CPU. CoroutineDispatcher به shutdown دستی نیاز ندارد و به طور خودکار مدیریت میشود. برای تنظیم دقیق، CoroutineDispatcher سفارشی خود را از طریق Executors.newFixedThreadPool(2).asCoroutineDispatcher() ایجاد کنید. کوروتینها جایگزین استخر نخها نمیشوند، بلکه آن را میپوشانند.
iOS دو مکانیزم اصلی برای مدیریت استخر نخها ارائه میدهد: OperationQueue (API سطح بالا مبتنی بر GCD) و GCD DispatchQueue (API سطح پایین C). OperationQueue استخر نخها را از طریق ویژگی maxConcurrentOperationCount کپسوله میکند. به طور پیشفرض، OperationQueue از system-defined maximum (وابسته به بار سیستم) استفاده میکند. DispatchQueue.global() یک صف همزمان با استخر نخ سیستمی ارائه میدهد.
OperationQueue استخر نخها را از طریق maxConcurrentOperationCount مدیریت میکند. مقدار ۱ یک صف ترتیبی ایجاد میکند (مشابه استخر تکنخی). مقدار بیشتر از ۱ — استخر همزمان با محدودیت مشخص. به طور پیشفرض maxConcurrentOperationCount = NSOperationQueueDefaultMaxConcurrentOperationCount (بهینه سیستم، معمولاً ۴-۸ نخ). Operation از وابستگیها، اولویتها و لغو پشتیبانی میکند. هر عملیات روی هر نخ آزاد از استخر سیستم اجرا میشود.
// OperationQueue با استخر ۳ نخ
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 3
queue.qualityOfService = .utility
// ایجاد عملیات
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) }
}
// وابستگی: operation2 منتظر operation1 است
operation2.addDependency(operation1)
// افزودن به صف
queue.addOperations([operation1, operation2], waitUntilFinished: false)
// لغو همه عملیات
queue.cancelAllOperations()
DispatchQueue — استخر نخ اپل است. صف همزمان (qos: .utility) از استخر نخ سیستمی استفاده میکند که برای بار فعلی دستگاه بهینه شده است. QoSهای مختلف (userInteractive, userInitiated, utility, background) به استخرهای مختلف با اولویتهای مختلف نگاشت میشوند. DispatchGroup امکان همگامسازی چندین وظیفه را فراهم میکند. DispatchWorkItem از لغو و qualityOfService پشتیبانی میکند. برای کنترل دقیق، صفهای همزمان خود را از طریق DispatchQueue(label: qos: attributes: .concurrent) ایجاد کنید.
// GCD DispatchQueue به عنوان استخر نخ
let customQueue = DispatchQueue(
label: "com.app.background",
qos: .utility,
attributes: .concurrent,
autoreleaseFrequency: .workItem
)
// ارسال وظایف به استخر
customQueue.async { self.processFile(file1) }
customQueue.async { self.processFile(file2) }
// 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() // هر دو وظیفه کامل شدند
}
// محدودیت همزمانی از طریق semaphore
let semaphore = DispatchSemaphore(value: 3)
for url in urls {
pool.async {
semaphore.wait()
download(url)
semaphore.signal()
}
}
پیکربندی استخر نخها مستقیماً بر عملکرد برنامه تأثیر میگذارد. پارامترهای نادرست منجر به استفاده ناقص از CPU (نخهای خیلی کم) یا بار اضافی روی سیستم (نخهای خیلی زیاد) میشود. در برنامههای موبایل، مقادیر بهینه به دلیل منابع محدود و مصرف انرژی با مقادیر سرور متفاوت است. پارامترهای اصلی: corePoolSize، maxPoolSize، queue capacity و keepAliveTime.
فرمول برای وظایف IO-bound: corePoolSize = تعداد هستههای CPU × ۲ (نخها منتظر ورودی-خروجی هستند). برای وظایف CPU-bound: corePoolSize = تعداد هستههای CPU (نخها دائماً مشغول محاسبات هستند). در دستگاههای موبایل مدرن (۶-۸ هسته) این مقادیر ۶-۸ نخ برای CPU-bound و ۱۲-۱۶ برای IO-bound را به دست میدهد. تستهای عملی نشان میدهد که برای یک برنامه موبایل معمولی ۳-۴ نخ بهینه است — نخهای بیشتر مصرف انرژی را افزایش میدهند بدون افزایش عملکرد.
اندازه صف وظایف (work queue) تعیین میکند که چند وظیفه میتوانند منتظر اجرا بمانند. صف نامحدود (LinkedBlockingQueue بدون محدودیت) میتواند در صورت ورود سریع وظایف منجر به OOM شود. صف محدود (ArrayBlockingQueue با اندازه ثابت) وظایف را هنگام سرریز رد میکند. برای برنامههای موبایل، ArrayBlockingQueue با ظرفیت ۱۶-۳۲ وظیفه توصیه میشود. CallerRunsPolicy — بهترین RejectedExecutionHandler برای دستگاههای موبایل: به جای از دست دادن وظیفه، فرستنده را کند میکند (pressure back).
| پارامتر | توصیه برای موبایل | دلیل |
|---|---|---|
| corePoolSize | ۲-۴ | منابع محدود دستگاه موبایل |
| maxPoolSize | corePoolSize (یا +۱-۲) | جلوگیری از بارهای اوج ایجاد نخ |
| keepAliveTime | ۱۵-۳۰ ثانیه | آزادسازی سریع حافظه، اما بدون ایجاد مکرر |
| Queue capacity | ۱۶-۳۲ | تعادل بین بافرینگ و ریسک OOM |
| Handler | CallerRunsPolicy | فشار برگشتی بدون از دست دادن وظایف |
توسعهدهندگان برنامههای موبایل اغلب هنگام استفاده از استخر نخها اشتباهاتی مرتکب میشوند که منجر به کرش، نشت حافظه و عملکرد ناپایدار میشود. رایجترین: فراخوانی نکردن shutdown() برای ExecutorService، ایجاد استخر جدید برای هر عملیات، استخر بسیار بزرگ، deadlock بین وظایف، استفاده از CachedThreadPool در Android.
Deadlock زمانی رخ میدهد که یک وظیفه در استخر منتظر نتیجه وظیفه دیگری از همان استخر است، اما همه نخها مشغول انتظار هستند. مثال: وظیفه A وظیفه B را به همان استخر میفرستد و future.get() را فراخوانی میکند — اگر استخر تمام شده باشد، وظیفه A منتظر وظیفه B میماند و وظیفه B نمیتواند اجرا شود زیرا نخ آزادی وجود ندارد. راهحل: از استخرهای جداگانه برای سطوح مختلف وظایف یا callback ناهمگام به جای .get() مسدودکننده استفاده کنید.
// Deadlock در Thread Pool
val pool = Executors.newFixedThreadPool(1)
// وظیفه A منتظر وظیفه B است — deadlock!
val futureA = pool.submit {
// این وظیفه هرگز اجرا نخواهد شد
val futureB = pool.submit { 42 }
futureB.get() // برای همیشه مسدود شده
}
// رفع: استخرهای جداگانه
val workerPool = Executors.newFixedThreadPool(2)
val callbackPool = Executors.newSingleThreadExecutor()
workerPool.submit {
callbackPool.submit {
// در استخر جداگانه اجرا میشود — deadlock غیرممکن است
}
}
// یا از CompletableFuture استفاده کنید
workerPool.submit {
CompletableFuture
.supplyAsync { 42 }
.thenAccept { result ->
println(result)
}
}
ExecutorService ایجاد شده در Activity باید در onDestroy() پایان یابد. اگر این کار انجام نشود، نخها حتی پس از نابودی Activity در حافظه باقی میمانند. راهحل: استخر را در محدوده Application یا ViewModel ذخیره کنید، نه در Activity. برای کوروتینها از viewModelScope یا lifecycleScope استفاده کنید. اگر استخر در داخل Activity ایجاد شده است، حتماً pool.shutdown() را در onDestroy() فراخوانی کنید. برای تست از es.shutdownNow() برای توقف فوری استفاده کنید.
سؤالات متداول
Thread Pool از نخهای از پیش ایجاد شده برای اجرای وظایف متعدد استفاده مجدد میکند. یک نخ معمولی (raw Thread) ایجاد میشود، یک وظیفه را اجرا میکند و نابود میشود. ایجاد نخ حدود ۱ مگابایت حافظه و ~۱ میلیثانیه زمان میبرد. Thread Pool هزینههای سربار را کاهش میدهد، حداکثر تعداد نخها را محدود میکند و API مدیریت (shutdown, awaitTermination) ارائه میدهد.
برای یک برنامه موبایل معمولی ۲-۴ نخ بهینه است. برای وظایف CPU-bound — تعداد هستههای CPU. برای وظایف IO-bound — تعداد هستهها × ۲. تعداد بیشتر نخها مصرف انرژی و تغییر زمینه را بدون افزایش عملکرد افزایش میدهد. در Android از Process.availableProcessors() برای تعیین هستهها استفاده کنید. در iOS — ProcessInfo.processInfo.processorCount.
CachedThreadPool در صورت نیاز نخ ایجاد میکند و نخهای موجود را مجدداً استفاده میکند. مشکل: حداکثر تعداد نخها را محدود نمیکند. اگر ۱۰۰ وظیفه همزمان برسند، ۱۰۰ نخ ایجاد میشود. این منجر به OOM در Android میشود (هر نخ ~۱ مگابایت). از newFixedThreadPool(n) با محدودیت صریح استفاده کنید. CachedThreadPool فقط برای وظایف burst کوتاهمدت با تضمین حجم کم مجاز است.
بله، اگر استخر به یک کانتینر مدیریتشده (مانند کوروتینها) تعلق نداشته باشد. shutdown() پذیرش وظایف جدید را متوقف میکند و نخها را پس از اتمام وظایف جاری پایان میدهد. بدون shutdown() نخها در حافظه باقی میمانند و برنامه پایان نمییابد. برای Activity در onDestroy() فراخوانی کنید. برای ViewModel از coroutineScope استفاده کنید. پایان دادن به استخر بخش الزامی مدیریت منابع است، مشابه بستن Cursor یا InputStream.
بله، OperationQueue و DispatchQueue استخر نخ ارائهشده توسط iOS هستند. OperationQueue همزمانی را از طریق maxConcurrentOperationCount محدود میکند. DispatchQueue.global() از استخر نخ سیستمی بدون کنترل مستقیم استفاده میکند. برخلاف Java ThreadPoolExecutor، شما corePoolSize یا queue capacity را مدیریت نمیکنید — سیستم استخر را به طور خودکار تحت بار فعلی و مصرف انرژی دستگاه بهینه میکند.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید