تعدد الخيوط والتزامن في تطوير تطبيقات الجوال: ما هو، مبادئه وكيف يعمل

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

كل تطبيق جوال ينفذ مهام متعددة في وقت واحد: تحميل البيانات من الشبكة، معالجة لمسات المستخدم، تحريك الواجهة وحفظ الملفات. إذا كان كل هذا الكود يعمل في خيط واحد، يتجمد التطبيق عند أي تأخير في الشبكة. تعدد الخيوط (multithreading) والتزامن هما مفهومان رئيسيان يسمحان للتطبيق بالبقاء سريع الاستجابة وفعالاً. في هذه المقالة سنستعرض جميع الأدوات الرئيسية: من Main Thread و RunLoop إلى Coroutines في Kotlin و Combine على iOS. المادة مبنية على الوثائق الرسمية لـ Apple GCD.

أهم النقاط

  • Main Thread — الخيط الوحيد للعمل مع واجهة المستخدم؛ جميع المهام الأخرى تُنقل إلى الخلفية
  • GCD و OperationQueue — الآليات الرئيسية لتعدد الخيوط في iOS
  • Coroutines و Flow — المعيار الحديث لعدم التزامن في Kotlin/Android
  • RxJava و RxSwift و Combine — أطر عمل تفاعلية للعمل مع تدفقات البيانات
  • Race Condition و Deadlock و Livelock — مشاكل كلاسيكية في تعدد الخيوط تتطلب المزامنة
  • اختيار الأداة يعتمد على المنصة وتعقيد المهمة: للاستدعاءات البسيطة يكفي Async/Await، للتدفقات المعقدة — Rx أو Combine

ما هو تعدد الخيوط؟

تعدد الخيوط هو قدرة التطبيق على تنفيذ عدة أجزاء من الكود في وقت واحد. كل جزء يُنفذ في خيط (Thread) منفصل — عملية خفيفة مع مكدس استدعاءات خاص بها. في تطوير تطبيقات الجوال، تنقسم الخيوط إلى فئتين: Main Thread (خيط واجهة المستخدم) و Background Threads (خيوط الخلفية).

نظام التشغيل نفسه يدير توزيع الخيوط على أنوية المعالج. الأجهزة الحديثة تحتوي على 6–8 أنوية، لذا فإن التنفيذ المتوازي يمكن أن يسرع العمل. ومع ذلك، إنشاء الخيوط هو عملية مكلفة، لذلك لا يُنصح بالعمل مباشرة مع Thread. بدلاً من ذلك، تُستخدم تجريدات مستوى أعلى: DispatchQueue و OperationQueue و CoroutineDispatcher.

التزامن (Concurrency) هو مفهوم أوسع من تعدد الخيوط. التزامن يعني أن المهام يمكن تنفيذها "بشكل متزامن" حتى على نواة واحدة من خلال تبديل السياق. عدم التزامن (Async/Await) هو نموذج برمجة حيث لا تمنع المهمة الخيط، بل تعيد التحكم أثناء انتظار النتيجة. اللغات الحديثة (Kotlin و Swift و Dart) تدعم Async/Await بشكل مدمج.

في IT Sectr، نولي اهتماماً خاصاً لمعمارية تعدد الخيوط الصحيحة في بداية المشروع. الأخطاء التي تُرتكب في المراحل المبكرة تؤدي إلى أخطاء يصعب اكتشافها: سباقات البيانات، الحظر المتبادل وعدم استقرار التطبيق تحت الحمل. كل مشروع من مشاريعنا يخضع لمراجعة معمارية التزامن في مرحلة التخطيط.

الخيوط الرئيسية (Main/Background)

Main Thread (الخيط الرئيسي) — الخيط الوحيد في تطبيق الجوال الذي لديه وصول إلى واجهة المستخدم. على Android يُسمى UI Thread، على iOS — Main Thread. جميع عمليات الواجهة — تغيير النص، الرسوم المتحركة، معالجة اللمسات — تُنفذ فقط على Main Thread. إذا تم تنفيذ عملية ثقيلة (تحميل ملف، تحليل JSON) على الخيط الرئيسي، تتوقف الواجهة عن الاستجابة. على Android يؤدي هذا إلى ANR (Application Not Responding)، على iOS — إلى "تجميد" الشاشة.

Background Threads (خيوط الخلفية) مخصصة لكل ما لا يتعلق بواجهة المستخدم: طلبات الشبكة، العمل مع قاعدة البيانات، معالجة الصور، التشفير. بعد الانتهاء، تُنقل النتيجة إلى Main Thread للعرض. كل منصة توفر أدواتها الخاصة للتبديل بين الخيوط: DispatchQueue.main.async في iOS، runOnUiThread أو withContext(Dispatchers.Main) في Android.

RunLoop — حلقة معالجة الأحداث على الخيط الرئيسي في iOS. RunLoop ينتظر الأحداث (اللمسات، المؤقتات، الإشعارات) ويوزعها على المعالجات المناسبة. على Android المكافئ هو Looper، المرتبط بكل Main Thread. Main Looper يستخرج الرسائل من قائمة الانتظار بلا نهاية ويمررها إلى Handler للمعالجة. فهم RunLoop و Looper يساعد في تجنب تسرب الذاكرة و "تقطّع" الواجهة.

GCD و OperationQueue (iOS)

Grand Central Dispatch (GCD) — مكتبة Apple لإدارة تعدد الخيوط على مستوى لغة C. GCD تعمل مع DispatchQueue — قوائم انتظار المهام. المطور لا ينشئ الخيوط يدوياً؛ GCD يدير مجموعة خيوط (Thread Pool)، موزعاً المهام على أنوية المعالج المتاحة. DispatchQueue نوعان: Serial Queue (قائمة انتظار تسلسلية — المهام تُنفذ واحدة تلو الأخرى) و Concurrent Queue (قائمة انتظار متزامنة — المهام يمكن أن تُنفذ في وقت واحد).

Main DispatchQueue — قائمة انتظار تسلسلية مرتبطة بالخيط الرئيسي. Global Queues — قوائم انتظار متزامنة بأولويات مختلفة (QoS — Quality of Service): userInteractive و userInitiated و utility و background. اختيار QoS الصحيح أمر حاسم للأداء: .userInteractive — للمهام التي تؤثر على واجهة المستخدم (الرسوم المتحركة، العرض)؛ .background — للمهام غير الحرجة زمنياً (المزامنة، تنظيف ذاكرة التخزين المؤقت).

OperationQueue — طبقة فوق GCD مع إمكانيات إضافية: إلغاء المهام، تعيين التبعيات بين العمليات، التحكم في العدد الأقصى للعمليات المتزامنة. العمليات — كائنات من فئة Operation (أو BlockOperation). مثال: إذا كنت بحاجة لتحميل صورة، ثم تطبيق فلتر، وبعدها فقط عرضها — OperationQueue مع التبعيات تتعامل مع هذا بشكل مثالي. في GCD كان عليك مزامنة هذه الخطوات يدوياً باستخدام DispatchGroup أو semaphore.

Async/Await في Swift 5.5+ — بديل حديث لـ GCD. الكلمات المفتاحية async و await تجعل الكود غير المتزامن خطياً وقابلاً للقراءة. الدوال تُوسم بـ async، والاستدعاءات تُنتظر عبر await. النظام نفسه يدير تبديل السياق: افتراضياً، دالة async تُنفذ على خيط خلفية، بينما تحديث واجهة المستخدم يُنفذ على MainActor. @MainActor — سمة تضمن تنفيذ الكود على الخيط الرئيسي.

Coroutines و Flow (Kotlin)

Coroutines (الكوروتينات) — خيوط خفيفة لـ Kotlin طورتها JetBrains. على عكس الخيوط العادية، الكوروتينات غير مرتبطة بخيط محدد. آلاف الكوروتينات يمكن أن تعمل على عدة خيوط دون أعباء كبيرة. CoroutineScope يدير دورة حياة الكوروتينات: viewModelScope مرتبط بـ ViewModel، lifecycleScope — بـ Activity/Fragment. عند تدمير النطاق، جميع الكوروتينات التابعة تُلغى تلقائياً.

Dispatchers تحدد على أي مجموعة خيوط يُنفذ الكوروتين: Dispatchers.Main — خيط واجهة المستخدم؛ Dispatchers.IO — لطلبات الشبكة وعمليات القرص؛ Dispatchers.Default — للحسابات المكثفة لوحدة المعالجة المركزية. لتبديل المرسل يُستخدم withContext. الكوروتينات تدعم التزامن المنظم (structured concurrency): كل كوروتين له أب، وعند إلغاء الأب تُلغى جميع الكوروتينات التابعة. هذا يمنع تسرب الذاكرة والمهام العالقة.

Flow — تدفق بيانات غير متزامن بارد من مكتبة الكوروتينات. Flow يصدر القيم بشكل تسلسلي: (1) المنتج يولد البيانات، (2) المشغلات تحول التدفق، (3) المجمع يستهلك النتيجة. على عكس LiveData، Flow يدعم سلاسل معقدة من المشغلات (map, filter, flatMapConcat, catch) وآمن تماماً للخيوط. StateFlow و SharedFlow — أنواع ساخنة من Flow، مثالية لحالة واجهة المستخدم والأحداث لمرة واحدة (Snackbar، التنقل).

Channel — تجريد آخر للكوروتينات لنقل البيانات بين الكوروتينات. Channel يعمل كقائمة انتظار: مرسل واحد (send) ومستقبل واحد أو أكثر (receive). القنوات المخزنة مؤقتاً (Channel(UNLIMITED)، Channel(BUFFERED)) تسمح بتكوين السلوك عند التدفق الزائد. Channel يُستخدم غالباً مع Flow لربط واجهات برمجة التطبيقات القائمة على الاستدعاء بالكوروتينات: callbackFlow { … }.

في IT Sectr نستخدم بنشاط الكوروتينات و Flow في جميع مشاريع Android. هذا يسمح بكتابة كود غير متزامن يبدو متزامناً، ويسهل اختباره (runTest, TestDispatcher) ولا يتطلب إدارة يدوية للخيوط. مثال لكوروتين بسيط مع تحميل البيانات:

kotlin
class UserRepository(
    private val api: UserApi,
    private val dao: UserDao
) {
    suspend fun getUsers(): List<User> = withContext(Dispatchers.IO) {
        return@withContext try {
            val users = api.fetchUsers()
            dao.insertAll(users)
            users
        } catch (e: Exception) {
            dao.getAll()
        }
    }
}

Rx و Combine

البرمجة التفاعلية — نموذج تنتشر فيه البيانات كتدفقات غير متزامنة (Observable, Publisher). RxJava/RxKotlin — التنفيذ الأكثر شعبية لـ Android، منقول من .NET Rx. RxSwift — مكتبة مماثلة لـ iOS. المكونات الرئيسية: Observable (مصدر الأحداث)، Observer (مشترك)، Scheduler (إدارة الخيوط)، Operators (تحويل التدفق).

Combine — إطار عمل Apple للبرمجة التفاعلية، مُقدَّم في iOS 13. Combine يستخدم بروتوكولات Publisher (ناشر) و Subscriber (مشترك). على عكس RxSwift، Combine مدمج في SDK ومتكامل بشكل وثيق مع SwiftUI. المشغلات في Combine: map, filter, combineLatest, zip, debounce, throttle — تغطي معظم السيناريوهات: من ربط البيانات بواجهة المستخدم إلى debounce لاستعلام البحث.

Future و Promise — أنماط للعمل مع نتيجة غير متزامنة واحدة. Future يمثل قيمة ستكون متاحة لاحقاً. Promise — وعد بتوفير قيمة. في Rx هذا هو Single (استجابة ناجحة واحدة أو خطأ)، في Combine — Future Publisher. عملياً، Future/Promise مناسبة للطلبات الفردية لواجهة برمجة التطبيقات، بينما Observable/Publisher — للتدفقات المستمرة (الموقع الجغرافي، إدخال النص).

Callback و Delegate — أنماط كلاسيكية للعمليات غير المتزامنة. Callback — دالة تُمرر كوسيطة وتُستدعى عند إتمام العملية. Delegate — كائن ينفذ بروتوكولاً مع طرق معالجة الأحداث. العيب: "callback hell" (استدعاءات متداخلة) وصعوبة معالجة الأخطاء. NotificationCenter (iOS) و EventBus (Android) — آليات بث الأحداث، مفيدة للاتصال ضعيف الاقتران ولكنها تؤدي إلى تبعيات ضمنية.

مشاكل تعدد الخيوط (Race Condition, Deadlock)

تعدد الخيوط يفتح الباب لأداء عالٍ، لكنه في نفس الوقت يخلق خطر أخطاء يصعب اكتشافها. الأكثر شيوعاً: Race Condition (حالة السباق)، Deadlock (الحظر المتبادل)، Livelock (الحظر النشط) و Starvation (تجويع الخيط). فهم هذه المشاكل هو مهارة إلزامية لأي مطور تطبيقات جوال.

Race Condition

Race Condition تنشأ عندما يقرأ ويكتب خيطان أو أكثر نفس البيانات في وقت واحد دون مزامنة. النتيجة تعتمد على أي خيط يُنفذ أولاً. مثال كلاسيكي: خيطان يزيدان عداداً. عملية "قراءة → زيادة → كتابة" ليست ذرية، لذلك عند التنفيذ المتزامن، تضيع زيادة واحدة. الحل — استخدام العمليات الذرية (AtomicInteger, AtomicReference) أو الأقفال (Mutex, Semaphore, synchronized).

Deadlock

Deadlock — حالة حيث كل خيط يمسك مورداً وينتظر مورداً محتجزاً من قبل خيط آخر. لا يمكن لأي خيط مواصلة التنفيذ. شروط الحدوث: الاستبعاد المتبادل، الاحتجاز والانتظار، عدم الاستباق، الانتظار الدائري. المنع: تعيين ترتيب واحد لالتقاط الأقفال، استخدام tryLock مع مهلة، تطبيق خوارزميات Lock-Free (ConcurrentHashMap, CopyOnWriteArrayList).

Livelock و Starvation

Livelock — الخيوط غير محظورة ولكنها تنقل الموارد لبعضها البعض باستمرار دون أداء عمل مفيد. مثال: شخصان يلتقيان في ممر وكلاهما يفسح الطريق، متحركين في نفس الاتجاه. Starvation — الخيط لا يحصل على وصول إلى المورد لأن خيوط أخرى تعترضه باستمرار. الحل: أقفال عادلة (fair locks)، أولويات الخيوط بحذر.

أدوات المزامنة

لمنع مشاكل تعدد الخيوط تُستخدم بدائيات المزامنة: Mutex (الاستبعاد المتبادل)، Semaphore (تحديد عدد الوصول المتزامن)، Lock (واجهة مع tryLock)، Synchronized (قفل على مستوى JVM)، @MainActor (Swift — ضمان التنفيذ على الخيط الرئيسي). على Android أيضاً متاح ThreadPool (مجموعة خيوط) عبر Executors.newFixedThreadPool، newCachedThreadPool. لكن الإدارة اليدوية للمجموعات هي من اختصاص المشاريع القديمة؛ في المشاريع الجديدة من الأفضل استخدام الكوروتينات.

الأداة المنصة النوع الميزات
DispatchQueue (GCD)iOSقائمة مهامتسلسلية/متزامنة، أولويات QoS، إدارة مجموعة الخيوط بواسطة النظام
OperationQueueiOSقائمة عملياتتبعيات، إلغاء، maxConcurrentOperationCount
Coroutines + FlowAndroidكوروتيناتخفيفة، تزامن منظم، StateFlow، Channel
RxJava / RxKotlinAndroidتدفق تفاعليObservable، Schedulers، مجموعة غنية من المشغلات
CombineiOSتدفق تفاعليPublisher/Subscriber، تكامل مع SwiftUI
Async/Await + TaskiOS / Androidنموذج غير متزامنكود خطي، @MainActor، تزامن منظم

الأسئلة المتكررة

ما الفرق بين Main Thread و Background Thread؟

Main Thread (خيط واجهة المستخدم) مسؤول عن عرض الواجهة ومعالجة اللمسات. Background Thread ينفذ مهام الخلفية — تحميل البيانات، الحسابات، العمل مع الشبكة. حظر Main Thread يسبب تجمد الواجهة (ANR على Android، frozen UI على iOS).

ما هي Race Condition وكيف تتجنبها؟

Race Condition — حالة سباق عندما يصل خيطان في وقت واحد إلى بيانات مشتركة وتعتمد النتيجة على ترتيب التنفيذ. تُتجنب بالمزامنة: Mutex, Semaphore, Lock, Synchronized، @MainActor أو العمليات الذرية.

Coroutines أم RxJava: ماذا تختار لـ Android؟

Coroutines — المعيار الحديث لـ Android (JetBrains، مدعوم من Google). RxJava/RxKotlin — نهج تفاعلي مع مجموعة غنية من المشغلات. Coroutines أبسط للاستدعاءات غير المتزامنة، RxJava أقوى للتدفقات المعقدة. في IT Sectr نستخدم Coroutines + Flow للمشاريع الجديدة.

ما هما Deadlock و Livelock؟

Deadlock — حظر متبادل حيث ينتظر خيطان موارد بعضهما البعض. Livelock — الخيوط غير محظورة ولكنها تنقل الموارد باستمرار دون عمل مفيد. كلا المشكلتين تُحلان بترتيب مناسب للأقفال والمهلات.

لماذا نحتاج DispatchQueue في iOS؟

DispatchQueue — تجريد لـ Grand Central Dispatch (GCD) لإدارة الخيوط. Main Queue ينفذ المهام على الخيط الرئيسي، Global Queues — على خيوط الخلفية. Serial Queue تضمن التنفيذ التسلسلي، Concurrent Queue — المتوازي. في المشاريع الحديثة، غالباً ما يُستبدل GCD بـ Async/Await و Task.

الخلاصة

  • Main Thread — فقط لواجهة المستخدم؛ جميع العمليات الأخرى في الخلفية
  • GCD و OperationQueue — أساس تعدد الخيوط على iOS؛ Async/Await — البديل الحديث
  • Coroutines و Flow — المعيار لـ Android؛ التزامن المنظم يمنع التسربات
  • RxJava و RxSwift و Combine — أطر تفاعلية للتدفقات المعقدة
  • Race Condition و Deadlock — المشاكل الرئيسية؛ تُحل بالأقفال وترتيب الحصول على الموارد
  • Thread Pool يُدار بواسطة النظام (GCD) أو الإطار (الكوروتينات)؛ إنشاء الخيوط يدوياً غير موصى به
  • اختيار الأداة يعتمد على المنصة: Coroutines لـ Android، GCD/Combine لـ iOS

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

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

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