Thread Pool — ایک تھریڈ مینجمنٹ میکانزم ہے جس میں پہلے سے بنایا گیا تھریڈز کا پول کاموں کو انجام دینے کے لیے دوبارہ استعمال کیا جاتا ہے، تھریڈ بنانے اور ختم کرنے کے اوورہیڈ سے بچتے ہوئے۔ موبائل ڈیولپمنٹ میں، تھریڈ پول پس منظر کے کاموں کے لیے استعمال ہوتا ہے: نیٹ ورک کی درخواستیں، تصویری پروسیسنگ، ڈیٹا بیس کے ساتھ کام۔ Google Android دستاویزات (2025) کے مطابق، ExecutorService Android میں پس منظر کے تھریڈز کو منظم کرنے کا تجویز کردہ طریقہ ہے۔ iOS میں، OperationQueue اور GCD DispatchQueue عالمی concurrent قطاروں کے ساتھ ایک جیسا کردار ادا کرتے ہیں۔
اہم نکات
Thread Pool (تھریڈ پول) — ایک آرکیٹیکچرل پیٹرن ہے جس میں تھریڈز کی ایک مقررہ تعداد پہلے سے بنائی جاتی ہے اور متعدد کاموں کو انجام دینے کے لیے دوبارہ استعمال کی جاتی ہے۔ ہر آپریشن کے لیے نیا تھریڈ بنانے کے بجائے (جو مہنگا ہے: JVM میں فی تھریڈ تقریباً 1 MB اسٹیک)، کاموں کو ایک قطار میں رکھا جاتا ہے اور پول کے دستیاب تھریڈز کے ذریعے انجام دیا جاتا ہے۔ موبائل ڈیولپمنٹ میں، تھریڈ پول کارکردگی کے لیے اہم ہے — Android اور iOS فی ایپلیکیشن تھریڈز کی تعداد کو محدود کرتے ہیں۔
تھریڈ بنانا ایک مہنگا آپریشن ہے: اسٹیک مختص، سسٹم رجسٹریشن، سیاق و سباق کی تبدیلی۔ محدود وسائل والے موبائل آلات پر، تھریڈ کا بے قابو تخلیق Android پر OOM (OutOfMemoryError) اور iOS پر تھروٹلنگ کا باعث بنتا ہے۔ Thread Pool دونوں مسائل حل کرتا ہے: یہ بیک وقت چلنے والے تھریڈز کی زیادہ سے زیادہ تعداد کو محدود کرتا ہے اور پہلے سے بنائے گئے تھریڈز کو دوبارہ استعمال کرتا ہے۔ Google raw Thread() کے بجائے ExecutorService تجویز کرتا ہے، Apple Thread کے بجائے OperationQueue تجویز کرتا ہے۔
| پیرامیٹر | پول کے بغیر (raw Thread) | Thread Pool کے ساتھ |
|---|---|---|
| تھریڈ تخلیق | ہر کام کے لیے | پول بناتے وقت ایک بار |
| زیادہ سے زیادہ تھریڈ | لا محدود (OOM خطرہ) | core/max pool size سے محدود |
| استعمال | کم (کام کے بعد تھریڈ ختم) | زیادہ (تھریڈ دوبارہ استعمال ہوتا ہے) |
| انتظام | دستی (join, interrupt) | خودکار (ExecutorService) |
| میموری کا استعمال | ہر کام کے ساتھ بڑھتا ہے | مقررہ |
تھریڈ پول پروڈیوسر کنزیومر اصول پر کام کرتا ہے: کام (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۔
// Android میں Thread Pool بنانا
val threadPool = ThreadPoolExecutor(
corePoolSize = 2, // کم از کم 2 تھریڈ
maximumPoolSize = 4, // زیادہ سے زیادہ 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() (ایک تھریڈ — ترتیب وار انجام)۔ موبائل پروجیکٹس کے لیے، مناسب حد (2-4 تھریڈ) کے ساتھ newFixedThreadPool تجویز کیا جاتا ہے، کیونکہ کیشڈ پول بہت زیادہ تھریڈ بنا سکتا ہے۔
ThreadPoolExecutor (TPE) — کنفیگر ہونے والے پیرامیٹرز کے ساتھ ExecutorService کا مکمل نفاذ۔ Android پر، TPE AsyncTask، IntentService اور JobIntentService کے اندر استعمال ہوتا ہے۔ پیرامیٹرز corePoolSize، maximumPoolSize، keepAliveTime، BlockingQueue اور RejectedExecutionHandler پول کے رویے کو باریک بینی سے ٹیون کرنے کی اجازت دیتے ہیں۔ Android کے لیے سفارشات: corePoolSize = CPU کور کی تعداد - 1 (IO-bound کاموں کے لیے) یا کور کی تعداد (CPU-bound کاموں کے لیے)۔ عام ایپلیکیشنز کے لیے: 2-4 تھریڈ۔
// Executors کی تیار کنفیگریشنز
// 1. 3 تھریڈ کا مقررہ پول
val fixedPool = Executors.newFixedThreadPool(3)
// 2. کیشڈ پول (موبائل کے لیے تجویز نہیں)
val cachedPool = Executors.newCachedThreadPool()
// 3. ایک تھریڈ (سیریلائزیشن)
val singlePool = Executors.newSingleThreadExecutor()
// 4. شیڈولر (متواتر کام)
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()
Kotlin کوروٹینز CoroutineDispatcher فراہم کرتی ہیں — تھریڈ پول کی طرح ایک تجرید۔ Dispatchers.IO 64 تھریڈز (محدود) کا پول استعمال کرتا ہے۔ Dispatchers.Default — CPU کور کی تعداد کے برابر پول۔ CoroutineDispatcher کو دستی بندش کی ضرورت نہیں ہے اور یہ خود بخود منظم ہوتا ہے۔ باریک بینی سے ٹیوننگ کے لیے، Executors.newFixedThreadPool(2).asCoroutineDispatcher() کے ذریعے کسٹم ExecutorCoroutineDispatcher بنائیں۔ کوروٹینز تھریڈ پول کو تبدیل نہیں کرتیں بلکہ اسے لپیٹ دیتی ہیں۔
iOS تھریڈ پول کے انتظام کے لیے دو اہم میکانزم فراہم کرتا ہے: OperationQueue (GCD پر بنایا گیا اعلیٰ سطحی API) اور GCD DispatchQueue (نیچی سطح کا C-API)۔ OperationQueue maxConcurrentOperationCount پراپرٹی کے ذریعے تھریڈ پول کو انکیپسلیٹ کرتا ہے۔ ڈیفالٹ طور پر، OperationQueue سسٹم کی وضاحت کردہ زیادہ سے زیادہ استعمال کرتا ہے (سسٹم لوڈ پر منحصر)۔ DispatchQueue.global() سسٹم تھریڈ پول کے ساتھ ایک concurrent قطار فراہم کرتا ہے۔
OperationQueue maxConcurrentOperationCount کے ذریعے تھریڈ پول کا انتظام کرتا ہے۔ قدر 1 ایک سیریل قطار بناتی ہے (ایک تھریڈ پول کے مشابہ)۔ 1 سے زیادہ قدر مخصوص حد کے ساتھ ایک concurrent پول بناتی ہے۔ ڈیفالٹ طور پر، maxConcurrentOperationCount = NSOperationQueueDefaultMaxConcurrentOperationCount (سسٹم بہترین، عام طور پر 4-8 تھریڈ)۔ Operation انحصار، ترجیحات اور منسوخی کو سپورٹ کرتا ہے۔ ہر آپریشن سسٹم پول کے کسی بھی دستیاب تھریڈ پر انجام پاتا ہے۔
// 3 تھریڈ کے پول کے ساتھ 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 — Apple کا تھریڈ پول ہے۔ ایک concurrent قطار (qos: .utility) سسٹم تھریڈ پول استعمال کرتی ہے، جو موجودہ ڈیوائس لوڈ کے لیے بہتر بنایا گیا ہے۔ مختلف QoS سطحیں (userInteractive, userInitiated, utility, background) مختلف ترجیحات والے مختلف پولز پر میپ ہوتی ہیں۔ DispatchGroup متعدد کاموں کو ہم آہنگ کرنے کی اجازت دیتا ہے۔ DispatchWorkItem منسوخی اور qualityOfService کو سپورٹ کرتا ہے۔ باریک کنٹرول کے لیے، DispatchQueue(label: qos: attributes: .concurrent) کے ذریعے کسٹم 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، قطار کی گنجائش اور keepAliveTime۔
IO-bound کاموں کے لیے فارمولا: corePoolSize = CPU کور کی تعداد × 2 (تھریڈ I/O کا انتظار کرتے ہیں)۔ CPU-bound کاموں کے لیے: corePoolSize = CPU کور کی تعداد (تھریڈ مسلسل حساب میں مصروف رہتے ہیں)۔ جدید موبائل آلات (6-8 کور) پر یہ CPU-bound کے لیے 6-8 تھریڈ اور IO-bound کے لیے 12-16 دیتا ہے۔ عملی ٹیسٹ ظاہر کرتے ہیں کہ عام موبائل ایپلیکیشن کے لیے 3-4 تھریڈ بہترین ہیں — زیادہ تھریڈ کارکردگی میں اضافے کے بغیر بجلی کی کھپت بڑھاتے ہیں۔
ورک قطار (work queue) کا سائز طے کرتا ہے کہ کتنے کام انجام پانے کا انتظار کر سکتے ہیں۔ لا محدود قطار (LinkedBlockingQueue بغیر حد کے) تیز کام کی آمد پر OOM کا سبب بن سکتی ہے۔ محدود قطار (مقررہ سائز والی ArrayBlockingQueue) بھرنے پر کاموں کو مسترد کرتی ہے۔ موبائل ایپلیکیشنز کے لیے، 16-32 کاموں کی گنجائش والی ArrayBlockingQueue تجویز کی جاتی ہے۔ CallerRunsPolicy موبائل کے لیے بہترین RejectedExecutionHandler ہے: یہ کام کھونے کے بجائے بھیجنے والے کو سست کرتا ہے (بیک پریشر)۔
| پیرامیٹر | موبائل کے لیے سفارش | وجہ |
|---|---|---|
| corePoolSize | 2-4 | موبائل ڈیوائس کے محدود وسائل |
| maxPoolSize | corePoolSize (یا +1-2) | تھریڈ تخلیق سے چوٹی کے بوجھ سے بچنا |
| keepAliveTime | 15-30 سیکنڈ | بار بار تخلیق کے بغیر تیز میموری ریلیز |
| قطار کی گنجائش | 16-32 | بفرنگ اور OOM خطرے کے درمیان توازن |
| ہینڈلر | CallerRunsPolicy | کام کھونے کے بغیر بیک پریشر |
موبائل ایپلیکیشن ڈیولپرز اکثر تھریڈ پول استعمال کرتے وقت غلطیاں کرتے ہیں جو کریش، میموری لیک اور غیر مستحکم آپریشن کا سبب بنتی ہیں۔ سب سے عام: ExecutorService کے لیے shutdown() نہ بلانا، ہر آپریشن کے لیے نیا پول بنانا، بہت بڑا پول، کاموں کے درمیان deadlock، Android پر CachedThreadPool استعمال کرنا۔
Deadlock اس وقت ہوتا ہے جب پول میں کوئی کام اسی پول کے کسی دوسرے کام کے نتیجے کا انتظار کرتا ہے، لیکن تمام تھریڈ انتظار میں مصروف ہوں۔ مثال: کام A اسی پول میں کام B بھیجتا ہے اور future.get() کال کرتا ہے — اگر پول ختم ہو جائے، کام A کام B کا انتظار کرتا ہے، اور کام B انجام نہیں پا سکتا کیونکہ کوئی خالی تھریڈ نہیں ہے۔ حل: مختلف سطح کے کاموں کے لیے علیحدہ پول استعمال کریں یا بلاک کرنے والے .get() کے بجائے async callback استعمال کریں۔
// Thread Pool میں Deadlock
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)
}
}
Activity میں بنایا گیا ExecutorService onDestroy() میں بند کیا جانا چاہیے۔ اگر ایسا نہ کیا جائے تو Activity تباہ ہونے کے بعد بھی تھریڈ میموری میں لٹکے رہیں گے۔ حل: پول کو Activity کے بجائے Application اسکوپ یا ViewModel میں رکھیں۔ کوروٹینز کے لیے، viewModelScope یا lifecycleScope استعمال کریں۔ اگر پول Activity کے اندر بنایا گیا ہے تو onDestroy() میں pool.shutdown() کال کرنا یقینی بنائیں۔ ٹیسٹ کے لیے، فوری روک کے لیے es.shutdownNow() استعمال کریں۔
اکثر پوچھے گئے سوالات
Thread Pool متعدد کاموں کو انجام دینے کے لیے پہلے سے بنائے گئے تھریڈز کو دوبارہ استعمال کرتا ہے۔ ایک عام تھریڈ (raw Thread) بنایا جاتا ہے، ایک کام انجام دیتا ہے اور ختم ہو جاتا ہے۔ تھریڈ بنانے میں تقریباً 1 MB میموری اور ~1 ms وقت لگتا ہے۔ Thread Pool اوورہیڈ کم کرتا ہے، تھریڈز کی زیادہ سے زیادہ تعداد کو محدود کرتا ہے اور انتظام کے لیے API (shutdown, awaitTermination) فراہم کرتا ہے۔
عام موبائل ایپلیکیشن کے لیے، 2-4 تھریڈ بہترین ہیں۔ CPU-bound کاموں کے لیے — CPU کور کی تعداد۔ IO-bound کاموں کے لیے — کور کی تعداد × 2۔ زیادہ تھریڈ کارکردگی میں اضافے کے بغیر بجلی کی کھپت اور سیاق و سباق کی تبدیلی بڑھاتے ہیں۔ Android پر، کور معلوم کرنے کے لیے Process.availableProcessors() استعمال کریں۔ iOS پر — ProcessInfo.processInfo.processorCount۔
CachedThreadPool ضرورت کے مطابق تھریڈ بناتا ہے اور موجودہ تھریڈز کو دوبارہ استعمال کرتا ہے۔ مسئلہ: یہ تھریڈز کی زیادہ سے زیادہ تعداد کو محدود نہیں کرتا۔ اگر ایک ساتھ 100 کام آئیں تو 100 تھریڈ بنائے جائیں گے۔ یہ Android پر OOM کا سبب بنتا ہے (ہر تھریڈ ~1 MB)۔ واضح حد کے ساتھ newFixedThreadPool(n) استعمال کریں۔ CachedThreadPool صرف یقینی چھوٹی مقدار والے قلیل مدتی برسٹ کاموں کے لیے قابل قبول ہے۔
ہاں، اگر پول کسی منظم کنٹینر (جیسے کوروٹینز) سے تعلق نہیں رکھتا۔ shutdown() نئے کام قبول کرنا بند کر دیتا ہے اور موجودہ کاموں کی تکمیل کے بعد تھریڈ ختم کر دیتا ہے۔ shutdown() کے بغیر، تھریڈ میموری میں لٹکے رہتے ہیں اور ایپلیکیشن ختم نہیں ہوتی۔ Activity کے لیے، اسے onDestroy() میں کال کریں۔ ViewModel کے لیے، coroutineScope استعمال کریں۔ پول بند کرنا وسائل کے انتظام کا ایک لازمی حصہ ہے، جیسے Cursor یا InputStream بند کرنا۔
ہاں، OperationQueue اور DispatchQueue iOS کے فراہم کردہ تھریڈ پول ہیں۔ OperationQueue maxConcurrentOperationCount کے ذریعے ہم آہنگی کو محدود کرتا ہے۔ DispatchQueue.global() براہ راست کنٹرول کے بغیر سسٹم تھریڈ پول استعمال کرتا ہے۔ Java ThreadPoolExecutor کے برعکس، آپ corePoolSize یا قطار کی گنجائش کا انتظام نہیں کرتے — سسٹم موجودہ لوڈ اور ڈیوائس بجلی کی کھپت کی بنیاد پر پول کو خود بخود بہتر بناتا ہے۔
خلاصہ
ہم ایک موبائل ایپلیکیشن ٹرنکی تیار کریں گے
IT Sectr 2017 سے اسٹارٹ اپس اور کاروبار کے لیے iOS اور Android ایپلیکیشنز بناتا ہے۔ ہم آپ کو مشورہ دیں گے اور بہترین حل تجویز کریں گے۔
مزید پڑھیں