انشاء بركة خيوط حاسوبية Thread Pools لتسريع قاعدة البيانات

Mohammad Laifمنذ 5 سنوات

بسم الله الرحمن الرحيم
السلام عليكم ورحمة الله وبركاته

 

في الدرس السابق رأينا طريقة استخدام نمط الـ الـ Thread Pools (الـ Executors) مع المنادى (الـ Callable) والكائنات المستقبلية (الـ Future) لإرجاع قيم من دالة الـ Insert لقاعدة البيانات الـ Room. في هذا الدرس سنتعلم طريقة استخدام هذا النمط لإنشاء بركة من الخيوط (خمسة خيوط) حتى تسرع لنا الاستعلام في قاعدة البيانات.

 

 قبل اكمال القرائه في هذا الدرس:

هذا الدرس قائم على استخدام قاعدة البيانات الـ Room والعديد من انماط التصميم الـ MVVM و Repository لذلك ستحتاج معرفه بسيطه بهم لفهم هذا الدرس. وبالمناسبة لقد قمت بكتابة دورة تتعلق بهذة الاشياء كلها تستطيع الاطلاع عليها في هذا الرابط: Android Architecture Components (دروس لتعلم Android Architecture Components من حزمة Android JetPack بلغة الجافا لتصميم تطبيقات الاندرويد).

 

استخدام الـ ThreadPools لإنشاء خيوط حاسوبية متعددة

في اغلب الاحيان عندما توجد لدينا قاعدة بيانات بها العديد من الحقول كـ ١٠٠ الف حقل (ككلمات وترجماتها) الاستعلام عن جميع هذه الحقول سوف يكون بطئ جداً. لذلك نستطيع تقسيم هذا العمل على عدت خيوط. فلنفرض ان لدينا ١٠٠٠٠٠ حقل ونريد الاستعلام عنهم في ان واحد! اذا قمنا بهذا على خيط واحد فسيعاني التطبيق من البطئ. لذلك من الافضل انشاء ٥ خيوط وتقسيم العمل عليهم ليتكفل كل خيط بالاستعلام عن ٢٠٠٠٠ فقط, وبذلك يكون المجموع ١٠٠٠٠٠.

 

لاحظ الصورة, جهاز نكسس يدعم ٨ خيوط حاسوبية. مايهمنا هو بركة الخيوط على اليمين.

لاحظ كيف نسلم العمل على شكل Runnable في التطبيق الى الـ Executor ثم يقوم الـ Executor بنقله الى الـ Work Queue الخاص به (تخيلها نوع من انواع الـ ArrayList). ثم يرى هل يوجد خيط من هذه الخمسة خيوط فارغ؟ حتى يتم وضع الـ Runnable عليه واطلاقه للمعالج.

 

في مثال هذا الدرس:

  • لدينا ١٠ اسماء, سنقوم بانشاء ٥ خيوط حاسوبيه, كل خيط مسؤول عن استعلام اسمين فقط, ثم نقوم بجمعهم (حاول التطبيق على بيانات اكثر لترى الفرق في السرعه بين هذا النمط وباقي الانماط).
  • سوف نقوم بأنشاء كلاس Runnalbe (كما هو بالدرس الـ انشاء الخيط الحاسوبي بواسطة واجهة الـ Runnable) تحتوي على شفرة برمجية للإستعلام عن حقول ما بقاعدة البيانات الـ Room بحسب معطيات محدده.

 

انشاء كلاس Runnable الذي سنرسله على الخيوط المتضمن لشفرة استعلام قاعدة البيانات:

public class MyThreadPoolRunnable implements Runnable{

    private static final String TAG = "MyThreadPoolRunnable";
    private int mStartAt;
    private int mChunkSize;
    private AppDatabase mAppDatabase;
    private WeakReference<Handler> mMainThreadHandler;

    public MyThreadPoolRunnable(int startAt, int chunkSize, AppDatabase appDatabase, Handler mainThreadHandler) {
        mStartAt = startAt;
        mChunkSize = chunkSize;
        mAppDatabase = appDatabase;
        mMainThreadHandler = new WeakReference<>(mainThreadHandler);
    }


    @Override
    public void run() {
        // Query the database, and parcel the result.
        ArrayList<NameEntity> names = new ArrayList<>(mAppDatabase.getNameDao().getNamesBetween(mStartAt, mChunkSize));

        for (int i = 0; i < names.size(); i++) {
            Log.d(TAG, "handleMessage: " + names.get(i).getName() + " - " + names.get(i).getNumber() + " - " + names.get(i).getId() + " | From Thread -> " + Thread.currentThread().getName());
        }

        Parcelable wrappedNames = Parcels.wrap(names);

        // Put the result into bundle.
        Bundle bundle = new Bundle();
        bundle.putParcelable(Constant.DATABASE_MULTI_QUERY_RESULT, wrappedNames);

        // Create message object and Put that bundle in it.
        Message message = Message.obtain(null, Constant.DATABASE_MULTI_QUERY_TASK);
        message.setData(bundle);

        // Send message to main thread handler
        mMainThreadHandler.get().sendMessage(message);
    }
}

الحقول:

  • الحقل mStartAt هو لتحديد من الحقل المراد البدء منه.
  • الحقل mChunkSize هو لتحديد حجم الحقول المراده.

الـ Constructor:

  • يأخد البداية لحقل قاعدة البيانات وحجم الحقول المراده, يأخد قاعدة بيانات الـ Room, يأخد Handler للخيط الرئيسئ (لماذا؟ حتى نتمكن من ارسال البيانات الى الخيط الرئيسي لاحقاً الا وهو الـ MainActiivty كما رأينا في الدروس الاولى). كذلك حقل الـ Handler جعلناه WeakReference كما فعلنا سابقاً في بعض الدروس حتى لايسبب Memory Leak.

الدالة Run:

  • قمنا بتجهيز عنصر لسته الـ names. وفي نفس السطر قمنا باستخدام الـ Dao لقاعدة البيانات الـ Room للإستعلام عن الحقول.
  • قمنا بتغليف النتيجة (عنصر الـ names)  في Parcelable ثم وضعناها في Bundle وإرسلناها بداخل Message الى الخيط الحاسوبي الرئيسي الا وهو الـ MainActivity حتى نستلم البيانات هناك.
  • لاحظ الـ Constructor فمن خلاله سوف نقوم بأدخال الحقل لقاعدة البيانات المراد البدء منه, وايضاً حجم الحقول المراده.

 

طريقة استقبال البيانات في الـ Activity:

نعمل implement للـ Handler.Callback ثم Override للدالة handleMessage (لإستلام البيانات من الخيوط, كما رأينا في الانماط البدائيه) والتي ستحتوي على نتيجة الخيوط وسنضع النتيجه في حقل ArrayList:

public class Main4Activity extends AppCompatActivity implements Handler.Callback {
    private Handler mMainThreadHandler;
    private ArrayList<NameEntity> mNames;
    ExecutorService mExecutorService;
...
..

    @Override
    public boolean handleMessage(Message msg) {
        if (msg.what == Constant.DATABASE_MULTI_QUERY_TASK) {
            ArrayList<NameEntity> names = Parcels.unwrap(msg.getData().getParcelable(Constant.DATABASE_MULTI_QUERY_RESULT));
            mNames.addAll(names);
        }

        return false;
    }

...
..

}

 

انشاء بركة الخيوط

في دالة الـ onCreate:

ننشئ ThreadPools باستخدام ExeutorService تحتوي على خمس خيوط:

mExecutorService = Executors.newFixedThreadPool(5);

 

قبل كل شئ يجب عليك معرفة معالج جهازك CPU كم نواة Core يحتوي وكم خيط حاسوبي به Thread (حتى لاتصبح مشاكل) من خلال الكود التالي:

int availableProcessors = Runtime.getRuntime().availableProcessors();

مثلاً جهازي Nexus 6p يحتوي على ٨ خيوط. من الافضل ان لايزيد عدد الخيوط التي تريد انشائها عن عدد الخيوط في جهاز المستخدم.

 

ثم نقوم بانشاء عناصر Runnable من الكلاس التي انشئناها سابقاً, وكل عنصر مسؤل عن استعلام خاص به:

mExecutorService.execute(new MyThreadPoolRunnable(0, 2, mAppDatabase, mMainThreadHandler)); // gets 10 and 9
mExecutorService.execute(new MyThreadPoolRunnable(2, 2, mAppDatabase, mMainThreadHandler)); // gets 8 and 7
mExecutorService.execute(new MyThreadPoolRunnable(4, 2, mAppDatabase, mMainThreadHandler)); // gets 6 and 5
mExecutorService.execute(new MyThreadPoolRunnable(6, 2, mAppDatabase, mMainThreadHandler)); // gets 4 and 3
mExecutorService.execute(new MyThreadPoolRunnable(8, 2, mAppDatabase, mMainThreadHandler)); // gets 2 and 1

لاحظ استخدام دالة execute لتشغيل الـ Runnable على الخيط, بعكس الـ Callable الذي نستخدم معه الدالة submit.

 

وسوف يكون الناتج هكذا:

D/MyThreadPoolRunnable: handleMessage: Ahmad6 - 0555555556 - 6 | From Thread -> pool-1-thread-2
D/MyThreadPoolRunnable: handleMessage: Ahmad4 - 0555555554 - 4 | From Thread -> pool-1-thread-1
D/MyThreadPoolRunnable: handleMessage: Ahmad5 - 0555555555 - 5 | From Thread -> pool-1-thread-2
D/MyThreadPoolRunnable: handleMessage: Ahmad3 - 0555555553 - 3 | From Thread -> pool-1-thread-1
D/MyThreadPoolRunnable: handleMessage: Ahmad2 - 0555555552 - 2 | From Thread -> pool-1-thread-4
D/MyThreadPoolRunnable: handleMessage: Ahmad9 - 0555555559 - 9 | From Thread -> pool-1-thread-5
D/MyThreadPoolRunnable: handleMessage: Ahmad1 - 0555555551 - 1 | From Thread -> pool-1-thread-4
D/MyThreadPoolRunnable: handleMessage: Ahmad8 - 0555555558 - 8 | From Thread -> pool-1-thread-3
D/MyThreadPoolRunnable: handleMessage: Ahmad7 - 0555555557 - 7 | From Thread -> pool-1-thread-3
D/MyThreadPoolRunnable: handleMessage: Ahmad10 - 05555555510 - 10 | From Thread -> pool-1-thread-5

كل خيط حاسوبي استعلم عن اسمين.

 

في هذا الدرس رأينا طريقة انشاء بركة من الخيوط وطريقة استخدامها والى هنا نتوقف مع الـ ThreadPools. ونتجه الى انماط اخرى كنمط الـ AsyncTask الذي انقذ الكثير منا في بداية تعلمنا الاندرويد ومازال ينقذنا.

 

رابط الكلاسات المستخدمه في هذا الدرس:

 

المصادر والمراجع:

للمزيد راجع درس المقدمة.

 

نهاية الدرس
لاتنسى تتبع الدرس والدورة كذلك لإشعارك عندما يتم التعديل على المتحوى او اضافة المزيد من المعلومات. ايضاً لاتنسى الاعجاب بالدرس ومشاركته مع الاخرين.

المحاضر

Mohammad Laif

محتوى الدورة

تمهيد
1 مقدمة
2 تعرف على التزامن (الـ Concurrency)
3 اخطاء التزامن (الـ Concurrency) الشائعة
العمليات Processes
1 الهيكلة الهندسية لبيئة نظام الاندرويد
2 طبقة الـ Android Runtime و العمليات Processes
3 انواع العمليات (Processes)
الخيوط الحاسوبية Threads
1 الخيوط الحاسوبية (Threads)
2 مكونات الخيوط الحاسوبية (Thread)
3 انشاء الخيط الحاسوبي
أنماط التصميم للخيوط الحاسوبية
1 انشاء نمط الـ Handler و Looper و Thread
2 انشاء نمط الـ HandlerThreads
3 ماهو نمط الـ Thread Pools (الـ Executors)
4 استخدام نمط الـ Thread Pools كـ Singleton
5 استخدام الـ Callable مع نمط الـ Thread Pools
6 انشاء بركة خيوط حاسوبية Thread Pools لتسريع قاعدة البيانات الدرس الحالي
7 انشاء نمط الـ AsyncTask
8 انشاء نمط الـ Loader
الـ Broadcasts
1 تعرف على الـ Broadcast Receiver
2 انشاء الـ Broadcast Receiver بشكل ثابت
3 انشاء الـ Broadcast Receiver بشكل ديناميكي
4 استخدام الـ Local Broadcast للتخاطب بين المكونات
الـ Services
1 تعرف على الـ Services
2 انشاء الـ Started Service
3 انشاء الـ Intent Service
4 انشاء الـ Bound Service
5 استخدام الـ ResultReceiver للتخاطب مع الـ Intent Service
6 استخدام الـ Broadcast للتخاطب مع الـ Started Service
الـ Alarm Manager
1 تعرف على الـ Alarm Manager
2 طرق استخدام الـ Alarm Manager
الـ Jobs
1 استخدام الـ Android JobSchedualer
2 استخدام الـ Firebase JobDispatcher
3 استخدام الـ WorkManager من حزمة JetPack

الكلمات الدليلية

عن الدرس

0 إعجاب
1 متابع
0 مشاركة
1767 مشاهدات
منذ 5 سنوات

التعليقات (0)

لايوجد لديك حساب في عالم البرمجة؟

تحب تنضم لعالم البرمجة؟ وتنشئ عالمك الخاص، تنشر المقالات، الدورات، تشارك المبرمجين وتساعد الآخرين، اشترك الآن بخطوات يسيرة !