Thread — واحد پایهای زمان پردازنده است که پشته مخصوص خود را دارد و مستقل از سایر نخها اجرا میشود. در توسعه موبایل از نخها برای اجرای موازی وظایف استفاده میشود تا رابط کاربری در طول عملیات طولانی پاسخگو بماند. اندروید از java.lang.Thread، Executors و Kotlin Coroutines پشتیبانی میکند، iOS — از Thread (Objective-C)، GCD و OperationQueue. طبق Android Thread Documentation، ایجاد نخ بومی مستلزم تخصیص ~1 مگابایت برای پشته توسط سیستمعامل است.
نکات اصلی
Thread (نخ اجرا) — توالی مستقل از دستورالعملها است که سیستمعامل میتواند آن را بر روی هسته CPU زمانبندی کند. هر فرایند (اپلیکیشن) حداقل یک نخ دارد — Main Thread. نخهای اضافی برای اجرای موازی وظایف ایجاد میشوند. هر نخ پشته برنامه مخصوص خود (با متغیرهای محلی)، شمارنده دستور (PC) و رجیسترها را دارد. حافظه heap برای همه نخهای فرایند مشترک است.
در سیستمعاملهای موبایل، نخها با چندوظیفگی پیشگیرانه (preemptive multitasking) زمانبندی میشوند: سیستمعامل میتواند در هر لحظه اجرای نخ را قطع کرده و کنترل را به نخ دیگری بدهد (context switch). تعویض زمینه عملیات پرخرجی است (1–10 میکروثانیه)، زیرا نیاز به ذخیرهسازی/بازیابی رجیسترهای CPU، بهروزرسانی TLB و پاکسازی کشها دارد. دقیقاً به همین دلیل تعداد بیش از حد نخها (صدها و هزارها) عملکرد را بدتر میکند — سیستمعامل زمان بیشتری را صرف تعویض میکند تا اجرا.
نخ و فرایند مفاهیم متفاوتی هستند. فرایند نمونهای از اپلیکیشن با حافظه مجازی اختصاصی است. نخ داخل فرایند این حافظه را با سایر نخها به اشتراک میگذارد. در اندروید هر کامپوننت اپلیکیشن (Activity، Service، BroadcastReceiver) در یک فرایند کار میکند، اما میتواند در نخهای مختلف اجرا شود. اپلیکیشن iOS نیز یک فرایند واحد است که امکان ایجاد نخهای اضافی از طریق GCD یا Thread را دارد.
هر نخ در Java/Kotlin (اندروید) و NSThread (iOS) پنج حالت را طی میکند: New (ایجاد شده)، Runnable (آماده اجرا)، Running (در حال اجرا بر روی CPU)، Blocked/Waiting (منتظر منبع یا اعلان)، Terminated (پایانیافته). انتقالها بین حالتها توسط زمانبند سیستمعامل و ابزارهای همگامسازی مدیریت میشود. توسعهدهنده میتواند بر اولویت نخ (Thread.setPriority()) و حالت آن (sleep، join، interrupt) تأثیر بگذارد.
در اندروید نخ در هنگام تلاش برای تصاحب مانیتور اشغالشده (synchronized)، فراخوانی Object.wait() یا Thread.sleep() به حالت Blocked میرود. در iOS — هنگام فراخوانی NSCondition.wait()، pthread_cond_wait() یا dispatch_semaphore_wait(). در حالت Blocked نخ CPU مصرف نمیکند، اما حافظه (پشته) اشغال میکند. نخ میتواند از نخ دیگر قطع شود (interrupted)، با دریافت InterruptedException (جاوا) یا بررسی isCancelled (Kotlin Coroutines).
| حالت | توضیح | روش انتقال |
|---|---|---|
| New | نخ ایجاد شده، اما اجرا نشده است | سازنده Thread() |
| Runnable | نخ آماده اجراست، منتظر CPU است | thread.start() |
| Running | نخ بر روی هسته CPU در حال اجراست | زمانبند سیستمعامل |
| Blocked/Waiting | نخ منتظر منبع، مانیتور یا اعلان است | synchronized، wait()، sleep() |
| Terminated | نخ run() را تمام کرده یا قطع شده است | run() تمام شد، interrupt() |
Context switch (تعویض زمینه) — عملیاتی است که در آن سیستمعامل وضعیت نخ فعلی (رجیسترها، PC، TLB) را ذخیره کرده و وضعیت ذخیرهشده نخ دیگر را بارگذاری میکند. در سیستمهای موبایل (Linux + ART، XNU برای iOS) تعویض زمینه 1–10 میکروثانیه طول میکشد. اگر نخ وظیفهای را در 100 میکروثانیه اجرا کند و تعویض زمینه 5 طول بکشد، 5٪ از زمان هدر میرود. برای به حداقل رساندن تعویض زمینه، iOS از GCD با work stealing و اندروید از پولهایی با fixedThreadCount استفاده میکند.
اندروید از java.lang.Thread سطح پایین تا کوروتینهای مدرن تکامل یافته است. هر سطح انتزاع امکانات بیشتری با هزینههای اضافی کمتر میدهد. Thread کلاس پایه است، اما ایجاد مستقیم آن توصیه نمیشود: نخ جدید توسط پول مدیریت نمیشود، نظارت و لغو آن دشوار است. AsyncTask (deprecated از API 30) گامی رو به جلو بود، اما از نشت حافظه و مدیریت نامناسب پیکربندی رنج میبرد.
HandlerThread — زیرکلاس ویژه Thread با Looper است که میتواند صف پیامها را پردازش کند. برای اجرای ترتیبی وظایف در نخ پسزمینه استفاده میشود، مثلاً نوشتن داده در Room یا فایلها. HandlerThread با فراخوانی start() ایجاد میشود، پس از آن از طریق Handler(handlerThread.looper) میتوان پیام و Runnable ارسال کرد. فراخوانی handlerThread.quit() Looper را متوقف و نخ را خاتمه میدهد.
// اندروید: Thread، HandlerThread و Executors
import android.os.Handler
import android.os.HandlerThread
import java.util.concurrent.Executors
class ThreadExample {
// 1. ایجاد مستقیم Thread (توصیه نمیشود)
fun directThread() {
val thread = Thread(Runnable {
Thread.sleep(1000)
print("نخ مستقیم اجرا شد")
})
thread.start()
}
// 2. HandlerThread برای وظایف ترتیبی پسزمینه
fun handlerThreadExample() {
val handlerThread = HandlerThread("BackgroundQueue")
handlerThread.start()
val handler = Handler(handlerThread.looper)
handler.post {
// اجرای ترتیبی در نخ پسزمینه
Thread.sleep(500)
print("HandlerThread: وظیفه انجام شد")
}
// توقف نخ (زمانی اجرا میشود که وظایف تمام شوند)
handlerThread.quitSafely()
}
// 3. Executors — پول نخ
fun executorExample() {
val executor = Executors.newFixedThreadPool(4)
for (i in 1..10) {
executor.execute {
print("Task $i on thread ${Thread.currentThread().getName()}")
}
}
executor.shutdown()
}
// 4. Kotlin Coroutines — استاندارد مدرن
suspend fun coroutineExample() = kotlinx.coroutines.withContext(
kotlinx.coroutines.Dispatchers.Default
) {
print("Coroutine on thread: ${Thread.currentThread().getName()}")
}
}
مثال ThreadExample هر چهار سطح انتزاع نخ را در اندروید نشان میدهد. ایجاد مستقیم Thread — پایینترین سطح و ناکارآمدترین رویکرد است. HandlerThread برای وظایف ترتیبی در پسزمینه مفید است. Executors.newFixedThreadPool(4) پولی از 4 نخ برای اجرای موازی حداکثر 10 وظیفه ایجاد میکند. Kotlin Coroutines با Dispatchers.Default — روشی مدرن، کارآمد و ایمن است.
HandlerThread — زیرکلاس تخصصی Thread با Looper داخلی و صف پیام است. با فراخوانی start() ایجاد میشود، پس از آن از طریق Handler(handlerThread.looper) میتوان Runnable و پیام ارسال کرد. HandlerThread وظایف را کاملاً ترتیبی اجرا میکند — وظیفه بعدی تا پایان یافتن وظیفه قبلی شروع نمیشود. این برای نوشتن داده در Room یا فایلها، جایی که ترتیب عملیات حیاتی است، راحت است. فراخوانی quitSafely() پس از پایان وظیفه جاری Looper را متوقف میکند.
iOS نیز سه سطح کار با نخها را ارائه میدهد. Thread (Thread در Swift، NSThread در Objective-C) — API سطح پایین که مستقیماً نخ بومی ایجاد میکند. GCD (Grand Central Dispatch) از طریق DispatchQueue — ابزار اصلی توسعهدهندگان iOS که بهطور خودکار پول نخها را مدیریت میکند. OperationQueue — انتزاع سطح بالا بر روی GCD با پشتیبانی از وابستگیها، اولویتها و لغو.
استفاده مستقیم از Thread در توسعه مدرن iOS بسیار نادر است — GCD تمام قابلیتهای لازم را با مدیریت خودکار حافظه و نخها فراهم میکند. Thread فقط برای موارد خاص استفاده میشود: تنظیم thread-local storage (threadDictionary)، ایجاد RunLoop برای نخ پسزمینه یا یکپارچهسازی با کتابخانههای C که pthread_t را انتظار دارند.
import Foundation
class ThreadManager {
// 1. Thread (سطح پایین)
func createThread() {
let thread = Thread {
// کد در نخ جدید اجرا میشود
print("Current thread: \(Thread.current)")
}
thread.name = "com.app.worker"
thread.qualityOfService = .utility
thread.start()
}
// 2. GCD — DispatchQueue
func gcdExample() {
// صف موازی
let queue = DispatchQueue(label: "com.app.concurrent",
qos: .utility,
attributes: .concurrent)
queue.async {
print("وظیفه ناهمگام GCD")
}
// Barrier برای همگامسازی نوشتن
queue.async(flags: .barrier) {
// دسترسی انحصاری هنگام نوشتن
print("Barrier write: دسترسی انحصاری")
}
}
// 3. OperationQueue با وابستگیها
func operationQueueExample() {
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 2
queue.qualityOfService = .background
let download = BlockOperation {
print("در حال دانلود...")
}
let process = BlockOperation {
print("در حال پردازش...")
}
let save = BlockOperation {
print("در حال ذخیره...")
}
// وابستگیها: download -> process -> save
process.addDependency(download)
save.addDependency(process)
queue.addOperations([download, process, save], waitUntilFinished: false)
}
}
// مجموعه thread-safe از طریق GCD barrier
class ThreadSafeArray<T> {
private var array: [T] = []
private let queue = DispatchQueue(label: "com.app.concurrent",
attributes: .concurrent)
var count: Int {
return queue.sync { array.count } // concurrent read
}
func append(_ element: T) {
queue.async(flags: .barrier) { // exclusive write
self.array.append(element)
}
}
}
کلاس ThreadSafeArray الگوی Concurrent Read / Exclusive Write را از طریق GCD barrier نشان میدهد. خواندن از طریق queue.sync{} بهطور موازی از چند نخ انجام میشود. نوشتن از طریق queue.async(flags: .barrier) تمام عملیات دیگر (هم خواندن و هم نوشتن) را تا پایان نوشتن مسدود میکند. این کارآمدتر از بلوکهای synchronized است، زیرا تا زمانی که نوشتنی وجود نداشته باشد، خوانندهها را مسدود نمیکند.
استفاده مستقیم از Thread در iOS در سه مورد توجیهپذیر است: برای thread-local storage (Thread.current.threadDictionary) — ذخیره دادههای مرتبط با نخ؛ برای ایجاد RunLoop خاص در نخ پسزمینه با performSelector:onThread:؛ برای یکپارچهسازی با کتابخانههای C/C++ که pthread_t را انتظار دارند. در تمام موارد دیگر GCD از طریق DispatchQueue ارجح است — بهطور خودکار پول نخها و مصرف انرژی را مدیریت میکند.
Race condition (شرایط رقابت) زمانی رخ میدهد که دو یا چند نخ بهطور همزمان به دادههای مشترک دسترسی پیدا کنند و حداقل یکی از آنها بنویسد. نتیجه به ترتیب اجرا (timing) بستگی دارد و غیرقابل پیشبینی است. برای جلوگیری از race condition از ابزارهای همگامسازی استفاده میشود. در توسعه موبایل قفلها (synchronized، NSLock)، عملیات اتمیک (AtomicInteger، خواص atomic در iOS) و صفها (serial queue) در دسترس هستند.
انتخاب ابزار به سناریو بستگی دارد. برای شمارندهها و پرچمهای ساده عملیات اتمیک (AtomicInteger، atomic property) کافی است. برای بخشهای بحرانی با چند عملیات — قفلها (synchronized، NSLock). برای ساختارهای داده پیچیده — serial DispatchQueue یا GCD barrier. درک قفلها آسانتر است، اما آنها مستعد deadlock و livelock هستند. صفها پیچیدهتر اما امنترند.
// همگامسازی در Android/Kotlin
import java.util.concurrent.atomic.AtomicInteger
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
class Counter {
// 1. AtomicInteger — برای شمارندههای ساده
private val atomicCount = AtomicInteger(0)
fun incrementAtomic() = atomicCount.incrementAndGet()
// 2. synchronized — برای بخشهای بحرانی
@Synchronized
fun synchronizedOperation() {
// فقط یک نخ در هر بار
doWork()
}
// 3. Mutex از کوروتینها — suspend-safe
private val mutex = Mutex()
suspend fun mutexOperation() {
mutex.withLock {
// protected code — thread-safe
doWork()
}
}
private fun doWork() { /* critical section */ }
}
// مثال deadlock: A قفل B را، B قفل A را قفل میکند
class DeadlockExample {
private val lockA = Any()
private val lockB = Any()
fun methodA() = synchronized(lockA) {
Thread.sleep(100)
synchronized(lockB) { print("OK") }
}
fun methodB() = synchronized(lockB) {
Thread.sleep(100)
synchronized(lockA) { print("OK") }
}
}
Counter سه رویکرد همگامسازی را نشان میدهد. AtomicInteger.incrementAndGet() — عملیات اتمیک بدون قفل (CAS). @Synchronized — مانیتور داخلی جاوا که کل شیء را قفل میکند. Mutex.withLock — موتکس کوروتینی که به جای قفل کردن نخ، کوروتین را معلق میکند (کارآمدتر). DeadlockExample یک deadlock کلاسیک را نشان میدهد: دو نخ قفلها را به ترتیب متفاوت تصاحب میکنند.
Thread Pool (پول نخ) — مجموعهای از نخهای از پیش ایجادشده است که برای اجرای وظایف دوباره استفاده میشوند. به جای ایجاد نخ جدید برای هر وظیفه (پرهزینه)، پول یک نخ آزاد را برمیدارد. اگر نخ آزادی نباشد، وظیفه در صف قرار میگیرد. پول بهطور خودکار اندازه را مدیریت میکند: در بار پیک نخهای جدید ایجاد میشوند، نخهای بیکار خاتمه مییابند. این سربار ایجاد نخ را دهها بار کاهش میدهد.
در اندروید Executors.newFixedThreadPool(4) پولی از 4 نخ ایجاد میکند. اگر همزمان 10 وظیفه بیاید، 4 وظیفه بلافاصله شروع میشوند و 6 وظیفه در صف منتظر میمانند. Executors.newCachedThreadPool() نخها را در صورت نیاز (بدون محدودیت) ایجاد میکند و نخهای بیکار را پس از 60 ثانیه خاتمه میدهد. برای iOS، GCD بهطور خودکار پول صفهای سراسری را فراهم میکند که اندازه آنها با تعداد هستههای CPU و بار فعلی مطابقت دارد.
در Kotlin Coroutines پولهای نخ در داخل dispatchکنندهها پنهان شدهاند. Dispatchers.Default از پولی با اندازه برابر تعداد هستههای CPU (حداقل 2) استفاده میکند. Dispatchers.IO — 64 نخ (برای صدها وظیفه IO-bound کافی است، زیرا اکثر آنها بدون اشغال CPU منتظر ورودی-خروجی میمانند). هر dispatchکننده بهطور خودکار پول را با بار مقیاسدهی میکند و در حالت بیکاری انرژی باتری را ذخیره میکند.
سوالات متداول
Thread — واحد پایه اجرای کد در اپلیکیشن است. هر فرایند میتواند نخهای زیادی داشته باشد که حافظه را به اشتراک میگذارند، اما پشته مخصوص خود را دارند. در توسعه موبایل از نخها برای اجرای موازی وظایف بدون مسدود کردن UI استفاده میشود. اندروید از Thread، Executors، HandlerThread و Coroutines استفاده میکند. iOS از Thread، GCD (DispatchQueue) و OperationQueue استفاده میکند.
ایجاد Thread نیاز به تخصیص ~1 مگابایت پشته در اندروید و ~512 کیلوبایت در iOS دارد — این عملیات پرهزینهای است. برای 1000 وظیفه، ایجاد مستقیم 1000 نخ به ~1 گیگابایت فقط برای پشتهها بهعلاوه هزینههای اضافی تعویض زمینه نیاز دارد. به جای Thread از پولها (Executors، GCD) یا کوروتینها استفاده کنید — آنها نخها را دوباره استفاده میکنند و سربار را دهها بار کاهش میدهند.
Race condition — رفتار غیرقابل پیشبینی هنگام دسترسی همزمان چند نخ به دادههای مشترک با نوشتن است. میتوان به سه روش از آن اجتناب کرد: استفاده از انواع اتمیک (AtomicInteger)، قفلها (synchronized، NSLock) یا سریالسازی دسترسی از طریق صف (DispatchQueue serial، Actor در Kotlin). بهترین روش — به حداقل رساندن حالت mutable مشترک و استفاده از immutability است.
Thread — شیء سیستمی بومی است که ~1 مگابایت پشته اشغال میکند و به هسته سیستمعامل وابسته است. کوروتین — واحد اجرای سبک Kotlin است که به نخ خاصی وابسته نیست و میتواند بدون قفل معلق شود (suspend). یک نخ میتواند هزاران کوروتین را اجرا کند. کوروتینها از نظر حافظه کارآمدترند و نوشتن کد ناهمگام بدون callbacks را ممکن میکنند.
Deadlock بهصورت قفل شدن کامل اپلیکیشن بدون ANR ظاهر میشود. در اندروید از Thread.getAllStackTraces() برای dump پشتههای همه نخها استفاده کنید — دو نخ منتظر قفل یکدیگر خواهند بود. در iOS — Thread.callStackSymbols. ابزارها: Android Studio Profiler (زبانه Threads)، Instruments (iOS، Thread State View). پیشگیری: قفلها را به ترتیب ثابت تصاحب کنید، از tryLock با تایماوت استفاده کنید.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید