Thread در توسعه موبایل — چیست، انواع و مدیریت نخ‌ها

نویسنده: IT Sectr منتشر شده: 2026-03-16 زمان مطالعه: 11 دقیقه

Thread — واحد پایه‌ای زمان پردازنده است که پشته مخصوص خود را دارد و مستقل از سایر نخ‌ها اجرا می‌شود. در توسعه موبایل از نخ‌ها برای اجرای موازی وظایف استفاده می‌شود تا رابط کاربری در طول عملیات طولانی پاسخگو بماند. اندروید از java.lang.Thread، Executors و Kotlin Coroutines پشتیبانی می‌کند، iOS — از Thread (Objective-C)، GCD و OperationQueue. طبق Android Thread Documentation، ایجاد نخ بومی مستلزم تخصیص ~1 مگابایت برای پشته توسط سیستم‌عامل است.

نکات اصلی

  • Thread — کوچک‌ترین واحد زمان‌بندی CPU: هر نخ مستقل است و پشته مخصوص خود را دارد
  • ایجاد نخ در اندروید ~1 مگابایت و در iOS 512 کیلوبایت پشته نیاز دارد، بنابراین پول‌ها از ایجاد مستقیم کارآمدترند
  • اندروید: Thread، Executors، HandlerThread، Coroutines — چهار سطح انتزاع نخ
  • iOS: Thread (سطح پایین)، GCD (DispatchQueue)، OperationQueue (سطح بالا)
  • Thread safety — دسترسی مشترک به داده‌های mutable نیازمند همگام‌سازی است: locks، atomic، serial queues

Thread چیست

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 استفاده می‌کند.

Thread در اندروید: از Thread تا Coroutines

اندروید از java.lang.Thread سطح پایین تا کوروتین‌های مدرن تکامل یافته است. هر سطح انتزاع امکانات بیشتری با هزینه‌های اضافی کمتر می‌دهد. Thread کلاس پایه است، اما ایجاد مستقیم آن توصیه نمی‌شود: نخ جدید توسط پول مدیریت نمی‌شود، نظارت و لغو آن دشوار است. AsyncTask (deprecated از API 30) گامی رو به جلو بود، اما از نشت حافظه و مدیریت نامناسب پیکربندی رنج می‌برد.

HandlerThread — زیرکلاس ویژه Thread با Looper است که می‌تواند صف پیام‌ها را پردازش کند. برای اجرای ترتیبی وظایف در نخ پس‌زمینه استفاده می‌شود، مثلاً نوشتن داده در Room یا فایل‌ها. HandlerThread با فراخوانی start() ایجاد می‌شود، پس از آن از طریق Handler(handlerThread.looper) می‌توان پیام و Runnable ارسال کرد. فراخوانی handlerThread.quit() Looper را متوقف و نخ را خاتمه می‌دهد.

kotlin
// اندروید: 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: وظایف ترتیبی پس‌زمینه

HandlerThread — زیرکلاس تخصصی Thread با Looper داخلی و صف پیام است. با فراخوانی start() ایجاد می‌شود، پس از آن از طریق Handler(handlerThread.looper) می‌توان Runnable و پیام ارسال کرد. HandlerThread وظایف را کاملاً ترتیبی اجرا می‌کند — وظیفه بعدی تا پایان یافتن وظیفه قبلی شروع نمی‌شود. این برای نوشتن داده در Room یا فایل‌ها، جایی که ترتیب عملیات حیاتی است، راحت است. فراخوانی quitSafely() پس از پایان وظیفه جاری Looper را متوقف می‌کند.

Thread در iOS: Thread، GCD و OperationQueue

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 را انتظار دارند.

swift
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 در برابر GCD: چه زمانی مستقیماً از Thread استفاده کنیم

استفاده مستقیم از Thread در iOS در سه مورد توجیه‌پذیر است: برای thread-local storage (Thread.current.threadDictionary) — ذخیره داده‌های مرتبط با نخ؛ برای ایجاد RunLoop خاص در نخ پس‌زمینه با performSelector:onThread:؛ برای یکپارچه‌سازی با کتابخانه‌های C/C++ که pthread_t را انتظار دارند. در تمام موارد دیگر GCD از طریق DispatchQueue ارجح است — به‌طور خودکار پول نخ‌ها و مصرف انرژی را مدیریت می‌کند.

همگام‌سازی نخ‌ها: locks، atomic، serial queues

Race condition (شرایط رقابت) زمانی رخ می‌دهد که دو یا چند نخ به‌طور همزمان به داده‌های مشترک دسترسی پیدا کنند و حداقل یکی از آن‌ها بنویسد. نتیجه به ترتیب اجرا (timing) بستگی دارد و غیرقابل پیش‌بینی است. برای جلوگیری از race condition از ابزارهای همگام‌سازی استفاده می‌شود. در توسعه موبایل قفل‌ها (synchronized، NSLock)، عملیات اتمیک (AtomicInteger، خواص atomic در iOS) و صف‌ها (serial queue) در دسترس هستند.

انتخاب ابزار به سناریو بستگی دارد. برای شمارنده‌ها و پرچم‌های ساده عملیات اتمیک (AtomicInteger، atomic property) کافی است. برای بخش‌های بحرانی با چند عملیات — قفل‌ها (synchronized، NSLock). برای ساختارهای داده پیچیده — serial DispatchQueue یا GCD barrier. درک قفل‌ها آسان‌تر است، اما آن‌ها مستعد deadlock و livelock هستند. صف‌ها پیچیده‌تر اما امن‌ترند.

kotlin
// همگام‌سازی در 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 کلاسیک را نشان می‌دهد: دو نخ قفل‌ها را به ترتیب متفاوت تصاحب می‌کنند.

پول‌های نخ: چرا Executors بهتر از Thread است

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 در توسعه موبایل چیست؟

Thread — واحد پایه اجرای کد در اپلیکیشن است. هر فرایند می‌تواند نخ‌های زیادی داشته باشد که حافظه را به اشتراک می‌گذارند، اما پشته مخصوص خود را دارند. در توسعه موبایل از نخ‌ها برای اجرای موازی وظایف بدون مسدود کردن UI استفاده می‌شود. اندروید از Thread، Executors، HandlerThread و Coroutines استفاده می‌کند. iOS از Thread، GCD (DispatchQueue) و OperationQueue استفاده می‌کند.

چرا ایجاد مستقیم Thread توصیه نمی‌شود؟

ایجاد Thread نیاز به تخصیص ~1 مگابایت پشته در اندروید و ~512 کیلوبایت در iOS دارد — این عملیات پرهزینه‌ای است. برای 1000 وظیفه، ایجاد مستقیم 1000 نخ به ~1 گیگابایت فقط برای پشته‌ها به‌علاوه هزینه‌های اضافی تعویض زمینه نیاز دارد. به جای Thread از پول‌ها (Executors، GCD) یا کوروتین‌ها استفاده کنید — آن‌ها نخ‌ها را دوباره استفاده می‌کنند و سربار را ده‌ها بار کاهش می‌دهند.

race condition چیست و چگونه از آن اجتناب کنیم؟

Race condition — رفتار غیرقابل پیش‌بینی هنگام دسترسی همزمان چند نخ به داده‌های مشترک با نوشتن است. می‌توان به سه روش از آن اجتناب کرد: استفاده از انواع اتمیک (AtomicInteger)، قفل‌ها (synchronized، NSLock) یا سریال‌سازی دسترسی از طریق صف (DispatchQueue serial، Actor در Kotlin). بهترین روش — به حداقل رساندن حالت mutable مشترک و استفاده از immutability است.

Thread چه تفاوتی با کوروتین دارد؟

Thread — شیء سیستمی بومی است که ~1 مگابایت پشته اشغال می‌کند و به هسته سیستم‌عامل وابسته است. کوروتین — واحد اجرای سبک Kotlin است که به نخ خاصی وابسته نیست و می‌تواند بدون قفل معلق شود (suspend). یک نخ می‌تواند هزاران کوروتین را اجرا کند. کوروتین‌ها از نظر حافظه کارآمدترند و نوشتن کد ناهمگام بدون callbacks را ممکن می‌کنند.

چگونه deadlock را در اپلیکیشن موبایل تشخیص دهیم؟

Deadlock به‌صورت قفل شدن کامل اپلیکیشن بدون ANR ظاهر می‌شود. در اندروید از Thread.getAllStackTraces() برای dump پشته‌های همه نخ‌ها استفاده کنید — دو نخ منتظر قفل یکدیگر خواهند بود. در iOS — Thread.callStackSymbols. ابزارها: Android Studio Profiler (زبانه Threads)، Instruments (iOS، Thread State View). پیشگیری: قفل‌ها را به ترتیب ثابت تصاحب کنید، از tryLock با تایم‌اوت استفاده کنید.

خلاصه

  • Thread — کوچک‌ترین واحد CPU: اجرای مستقل با پشته مخصوص، حافظه heap مشترک است
  • پنج حالت نخ: New، Runnable، Running، Blocked/Waiting، Terminated
  • اندروید از Thread → AsyncTask → Executors → HandlerThread → Coroutines تکامل یافته است
  • iOS Thread، GCD (DispatchQueue) و OperationQueue — از سطح پایین به بالا ارائه می‌دهد
  • Race condition با قفل‌ها (synchronized، NSLock)، انواع اتمیک و صف‌های serial حل می‌شود
  • Deadlock هنگام تصاحب متقاطع قفل‌ها رخ می‌دهد — با ترتیب ثابت جلوگیری می‌شود
  • Thread Pool از ایجاد Thread جدید کارآمدتر است: نخ‌ها را دوباره استفاده می‌کند، سربار تعویض زمینه را کاهش می‌دهد

ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد

IT Sectr از سال 2017 برنامه‌های iOS و Android را برای استارتاپ‌ها و کسب‌وکارها ایجاد می‌کند. ما به شما مشاوره می‌دهیم و بهترین راه‌حل را پیشنهاد خواهیم کرد.

بحث درباره پروژه

همچنین بخوانید