الخيط (Thread) في تطوير التطبيقات المحمولة — ما هو، الأنواع وإدارة الخيوط

المؤلف: IT Sectr نُشر: 2026-03-16 وقت القراءة: 11 دق

Thread هي الوحدة الأساسية لوقت وحدة المعالجة المركزية (CPU)، ولها مكونها الخاص (stack) وتنفذ بشكل مستقل عن الخيوط الأخرى. في تطوير التطبيقات المحمولة، تستخدم الخيوط لتنفيذ المهام بالتوازي للحفاظ على استجابة الواجهة أثناء العمليات الطويلة. يدعم Android java.lang.Thread، Executors و Kotlin Coroutines، و iOS — Thread (Objective-C)، GCD و OperationQueue. وفقًا لـ وثائق Android Thread، يتطلب إنشاء خيط أصلي تخصيص حوالي ~1 ميغابايت للمكون المخصص من قبل نظام التشغيل.

النقاط الرئيسية

  • Thread — أصغر وحدة جدولة لوحدة المعالجة المركزية: كل خيط مستقل وله مكونه الخاص
  • إنشاء خيط يتطلب ~1 ميغابايت للمكون في Android و 512 كيلوبايت في iOS، لذا فإن المجموعات (pools) أكفأ من الإنشاء المباشر
  • Android: Thread، Executors، HandlerThread، Coroutines — أربعة مستويات لتجريد الخيوط
  • iOS: Thread (منخفض المستوى)، GCD (DispatchQueue)، OperationQueue (عالي المستوى)
  • أمان الخيوط — الوصول المشترك إلى البيانات القابلة للتغيير يتطلب مزامنة: الأقفال (locks)، الذرية (atomic)، الطوابير التسلسلية (serial queues)

ما هو Thread

Thread (خيط التنفيذ) هو تسلسل مستقل من التعليمات الذي يمكن لنظام التشغيل جدولته على نواة وحدة المعالجة المركزية. يحتوي كل عملية (تطبيق) على خيط واحد على الأقل — الخيط الرئيسي (Main Thread). يتم إنشاء خيوط إضافية لتنفيذ المهام بالتوازي. لكل خيط مكونه الخاص بالبرنامج (مع المتغيرات المحلية)، عداد الأوامر (PC) والمسجلات. ذاكرة الكومة (heap) مشتركة بين جميع خيوط العملية.

في أنظمة التشغيل المحمولة، تتم جدولة الخيوط باستخدام تعدد المهام الاستباقي (preemptive multitasking): يمكن لنظام التشغيل مقاطعة تنفيذ الخيط في أي لحظة وتسليم التحكم إلى آخر (تبديل السياق). تبديل السياق هي عملية مكلفة (1–10 ميكروثانية) لأنها تتطلب حفظ/استعادة مسجلات وحدة المعالجة المركزية، تحديث TLB وتفريغ الذاكرة المخبئة. لذلك فإن العدد المفرط من الخيوط (المئات أو الآلاف) يقلل من الأداء — يقضي نظام التشغيل وقتًا أكثر في التبديل من التنفيذ.

الخيط مقابل العملية — مفاهيم مختلفة. العملية هي نسخة من تطبيق بذاكرة افتراضية مخصصة. الخيط داخل العملية يشارك هذه الذاكرة مع الخيوط الأخرى. في Android، كل مكون من مكونات التطبيق (Activity، Service، BroadcastReceiver) يعمل في عملية واحدة ولكن يمكنه التنفيذ على خيوط مختلفة. تطبيق iOS هو أيضًا عملية واحدة قادرة على إنشاء خيوط إضافية من خلال GCD أو Thread.

دورة حياة الخيط: الحالات والانتقالات

كل خيط في Java/Kotlin (Android) و NSThread (iOS) يمر بخمس حالات: جديد (New)، قابل للتنفيذ (Runnable)، قيد التنفيذ (Running)، محجوب/منتظر (Blocked/Waiting)، منهي (Terminated). تتم إدارة الانتقالات بين الحالات بواسطة مجدول نظام التشغيل وأدوات المزامنة. يمكن للمطور التأثير على أولوية الخيط (Thread.setPriority()) وحالته (sleep، join، interrupt).

في Android، يدخل الخيط إلى حالة Blocked عند محاولة الحصول على مراقب مشغول (synchronized)، أو استدعاء Object.wait() أو Thread.sleep(). في iOS — عند استدعاء NSCondition.wait()، pthread_cond_wait() أو dispatch_semaphore_wait(). في حالة Blocked، لا يستهلك الخيط وحدة المعالجة المركزية ولكنه لا يزال يشغل ذاكرة (مكون). يمكن مقاطعة الخيط من خيط آخر، والحصول على InterruptedException (Java) أو التحقق من isCancelled (Kotlin Coroutines).

الحالةالوصفطريقة الانتقال
Newتم إنشاء الخيط ولكن لم يبدأمنشئ Thread()
Runnableالخيط جاهز للتنفيذ، ينتظر وحدة المعالجة المركزيةthread.start()
Runningالخيط يتنفذ على نواة وحدة المعالجة المركزيةمجدول نظام التشغيل
Blocked/Waitingالخيط ينتظر موردًا أو مراقبًا أو إشعارًاsynchronized، wait()، sleep()
Terminatedاكتمل الخيط run() أو تم مقاطعتهrun() اكتمل، interrupt()

تبديل السياق وتكلفته

تبديل السياق هي عملية حيث يقوم نظام التشغيل بحفظ حالة الخيط الحالي (المسجلات، PC، TLB) وتحميل الحالة المحفوظة لخيط آخر. في الأنظمة المحمولة (Linux + ART، XNU لـ iOS) يستغرق تبديل السياق 1–10 ميكروثانية. إذا كان الخيط ينفذ مهمة خلال 100 ميكروثانية ويستغرق تبديل السياق 5، فإن 5% من الوقت يضيع. لتقليل تبديل السياق، يستخدم iOS GCD مع work stealing، ويستخدم Android مجموعات مع fixedThreadCount.

Thread في Android: من Thread إلى Coroutines

Android تطور من java.lang.Thread منخفض المستوى إلى الكوروتينات الحديثة. كل مستوى تجريد يوفر قدرات أكبر مع تكلفة أقل. Thread هو الفئة الأساسية، ولكن لا يوصى بإنشائه مباشرة: الخيط الجديد لا يتم إدارته بواسطة مجموعة، ومن الصعب مراقبته وإلغاؤه. كان AsyncTask (مهجور منذ API 30) خطوة إلى الأمام، ولكنه عانى من تسرب الذاكرة ومعالجة غير مريحة للتكوينات.

HandlerThread هو فئة فرعية خاصة من Thread مع Looper يمكنه معالجة طابور الرسائل. يستخدم للتنفيذ التسلسلي للمهام على خيط خلفي، على سبيل المثال، كتابة البيانات إلى Room أو الملفات. يتم إنشاء HandlerThread باستدعاء start()، بعدها يمكن إرسال الرسائل و Runnable عبر Handler(handlerThread.looper). يقوم handlerThread.quit() بإيقاف Looper وإنهاء الخيط.

kotlin
// Android: 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("Direct thread executed")
        })
        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 المستويات الأربعة لتجريد الخيوط في Android. الإنشاء المباشر لـ Thread هو النهج الأكثر انخفاضًا وأقلها كفاءة. HandlerThread مفيد للمهام التسلسلية في الخلفية. Executors.newFixedThreadPool(4) ينشئ مجموعة من 4 خيوط لتنفيذ ما يصل إلى 10 مهام بالتوازي. Kotlin Coroutines مع Dispatchers.Default هو نهج حديث، فعال وآمن.

HandlerThread: مهام تسلسلية في الخلفية

HandlerThread هو فئة فرعية متخصصة من Thread مع Looper مضمن وطابور رسائل. يتم إنشاؤه باستدعاء start()، بعدها يمكن إرسال Runnable ورسائل عبر Handler(handlerThread.looper). ينفذ 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 فقط لحالات محددة: تخزين محلي للخيط (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 async task")
        }

        // حاجز لمزامنة الكتابة
        queue.async(flags: .barrier) {
            // وصول حصري أثناء الكتابة
            print("Barrier write: exclusive access")
        }
    }

    // 3. OperationQueue مع التبعيات
    func operationQueueExample() {
        let queue = OperationQueue()
        queue.maxConcurrentOperationCount = 2
        queue.qualityOfService = .background

        let download = BlockOperation {
            print("Downloading...")
        }
        let process = BlockOperation {
            print("Processing...")
        }
        let save = BlockOperation {
            print("Saving...")
        }

        // التبعيات: download -> process -> save
        process.addDependency(download)
        save.addDependency(process)

        queue.addOperations([download, process, save], waitUntilFinished: false)
    }
}

// مجموعة thread-safe عبر حاجز GCD
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 نمط القراءة المتزامنة / الكتابة الحصرية باستخدام حاجز GCD. تتم القراءة عبر queue.sync{} بالتوازي من خيوط متعددة. تقوم الكتابة عبر queue.async(flags: .barrier) بحجب جميع العمليات الأخرى (قراءة وكتابة) حتى اكتمال الكتابة. هذا أكفأ من كتل المزامنة (synchronized) لأنه لا يحجب القراء عندما لا توجد كتابة.

iOS Thread vs GCD: متى تستخدم Thread مباشرة

يتبرر استخدام Thread مباشرة في iOS في ثلاث حالات: للتخزين المحلي للخيط (Thread.current.threadDictionary) — تخزين بيانات مرتبطة بالخيط؛ لإنشاء RunLoop خاص على خيط خلفي مع performSelector:onThread:؛ للتكامل مع مكتبات C/C++ التي تتوقع pthread_t. في جميع الحالات الأخرى، يفضل GCD عبر DispatchQueue — يدير تلقائيًا مجموعة الخيوط واستهلاك الطاقة.

مزامنة الخيوط: الأقفال، الذرية، الطوابير التسلسلية

Race condition تحدث عندما يصل خيطان أو أكثر في نفس الوقت إلى بيانات مشتركة وواحد على الأقل يكتب. تعتمد النتيجة على توقيت التنفيذ وهي غير قابلة للتنبؤ. تستخدم أدوات المزامنة لمنع race condition. في تطوير التطبيقات المحمولة، تشمل الأدوات المتاحة الأقفال (synchronized، NSLock)، العمليات الذرية (AtomicInteger، خصائص atomic في iOS) والطوابير (طابور تسلسلي).

اختيار الأداة يعتمد على السيناريو. للعدادات والعلامات البسيطة، تكفي العمليات الذرية (AtomicInteger، خاصية ذرية). للمقاطع الحرجة بعمليات متعددة — الأقفال (synchronized، NSLock). لهيكل البيانات المعقدة — DispatchQueue تسلسلي أو حاجز GCD. الأقفال أسهل في الفهم ولكنها عرضة للأقفال المميتة (deadlocks) والأقفال الحية (livelocks). الطوابير أكثر تعقيدًا ولكنها أكثر أمانًا.

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 — آمن للخيوط
            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 — مراقب مضمن في Java، يحجب الكائن بأكمله. Mutex.withLock — موتكس كوروتين، يعلق الكوروتين بدلاً من حجب الخيط (أكفاء). يظهر DeadlockExample حالة اقفال مميت كلاسيكية: خيطان يحصلان على الأقفال بترتيب مختلف.

مجموعات الخيوط: لماذا Executors أفضل من Thread

Thread Pool (مجموعة خيوط) هي مجموعة من الخيوط المنشأة مسبقًا التي يتم إعادة استخدامها لتنفيذ المهام. بدلاً من إنشاء خيط جديد لكل مهمة (مكلف)، تأخذ المجموعة خيطًا متاحًا من المجموعة. إذا لم يكن هناك خيوط متاحة، توضع المهمة في طابور. تدير المجموعة حجمها تلقائيًا: تنشأ خيوط جديدة تحت الأحمال الذروية، وتنتهي الخيوط الخاملة. يقلل هذا التكلفة الإضافية لإنشاء الخيوط بعشرات المرات.

في Android، Executors.newFixedThreadPool(4) ينشئ مجموعة من 4 خيوط. إذا وصلت 10 مهام في نفس الوقت، 4 تبدأ التنفيذ فورًا، 6 تنتظر في الطابور. Executors.newCachedThreadPool() ينشئ خيوطًا حسب الحاجة (بدون حد) وينهي الخاملة بعد 60 ثانية. لـ iOS، يوفر GCD تلقائيًا مجموعات طابور عالمية يتوافق حجمها مع عدد نوى وحدة المعالجة المركزية والحمل الحالي.

في Kotlin Coroutines، تخفى مجموعات الخيوط داخل الموزعات (dispatchers). Dispatchers.Default يستخدم مجموعة حجمها = عدد نوى وحدة المعالجة المركزية (حد أدنى 2). Dispatchers.IO — 64 خيطًا (يكفي لمئات المهام المقيدة بالإدخال/الإخراج لأن معظمها ستنتظر الإدخال/الإخراج، ولا تشغل وحدة المعالجة المركزية). يقوم كل موزع تلقائيًا بتحجيم المجموعة تحت الحمل، مما يوفر طاقة البطارية عند الخمول.

الأسئلة الشائعة

ما هو Thread في تطوير التطبيقات المحمولة؟

Thread هي الوحدة الأساسية لتنفيذ الكود في تطبيق. يمكن لكل عملية أن تحتوي على عدة خيوط تتقاسم الذاكرة ولكن لكل منها مكونها الخاص. في تطوير التطبيقات المحمولة، تستخدم الخيوط لتنفيذ المهام بالتوازي دون حجب واجهة المستخدم. يستخدم Android Thread، Executors، HandlerThread و Coroutines. يستخدم iOS Thread، GCD (DispatchQueue) و OperationQueue.

لماذا لا يوصى بإنشاء Thread مباشرة؟

إنشاء Thread يتطلب تخصيص ~1 ميغابايت للمكون في Android و ~512 كيلوبايت في iOS — هذه عملية مكلفة. لـ 1000 مهمة، إنشاء 1000 خيط مباشرة سيتطلب ~1 جيجابايت فقط للمكامب بالإضافة إلى تكلفة تبديل السياق. بدلاً من Thread، استخدم المجموعات (Executors، GCD) أو الكوروتينات — تعيد استخدام الخيوط، مما يقلل التكلفة الإضافية بعشرات المرات.

ما هي race condition وكيف أتجنبها؟

Race condition هي سلوك غير قابل للتنبؤ عندما تصل خيوط متعددة في نفس الوقت إلى بيانات مشتركة مع كتابات. يمكن تجنبها بثلاث طرق: استخدام الأنواع الذرية (AtomicInteger)، الأقفال (synchronized، NSLock)، أو تسلسل الوصول عبر طابور (DispatchQueue serial، Actor في Kotlin). أفضل ممارسة هي تقليل الحالة المشتركة القابلة للتغيير واستخدام عدم القابلية للتغيير (immutability).

ما الفرق بين Thread والكوروتين؟

Thread هي كائن نظامي أصلي يشغل ~1 ميغابايت من المكون وهي مرتبطة بنواة نظام التشغيل. الكوروتين هي وحدة تنفيذ خفيفة في Kotlin غير مرتبطة بخيط محدد ويمكنها التعلق دون حجب. يمكن لخيط واحد تنفيذ آلاف الكوروتينات. الكوروتينات أكثر كفاءة في استخدام الذاكرة وتسمح بكتابة كود غير متزامن دون استدعاءات عود (callbacks).

كيف أكتشف deadlock في تطبيق محمول؟

Deadlock يظهر كتجمد كامل للتطبيق دون ANR. في Android، استخدم Thread.getAllStackTraces() لتفريغ مخزونات جميع الخيوط — سيكون خيطان في انتظار أقفال بعضهما البعض. في iOS — Thread.callStackSymbols. الأدوات: Android Studio Profiler (علامة تبويب Threads)، Instruments (iOS، Thread State View). الوقاية: احصل على الأقفال بترتيب ثابت، استخدم tryLock مع مهلة زمنية.

الملخص

  • Thread — أصغر وحدة في وحدة المعالجة المركزية: تنفيذ مستقل بمكونها الخاص، ذاكرة الكومة مشتركة
  • خمس حالات للخيط: New، Runnable، Running، Blocked/Waiting، Terminated
  • Android تطور من Thread → AsyncTask → Executors → HandlerThread → Coroutines
  • iOS يوفر Thread، GCD (DispatchQueue) و OperationQueue — من المنخفض إلى العالي
  • Race condition تحل بالأقفال (synchronized، NSLock)، الأنواع الذرية والطوابير التسلسلية
  • Deadlock يحدث نتيجة الحصول على الأقفال بشكل متقاطع — يمنع بترتيب ثابت
  • Thread Pool أكفاء من إنشاء Thread جديد: يعيد استخدام الخيوط، يقلل تكلفة تبديل السياق

سنقوم بتطوير تطبيق جوال جاهز

تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.

مناقشة المشروع

اقرأ أيضًا