انشاء بركة خيوط حاسوبية Thread Pools لتسريع قاعدة البيانات
بسم الله الرحمن الرحيم
السلام عليكم ورحمة الله وبركاته
في الدرس السابق رأينا طريقة استخدام نمط الـ الـ 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 الذي انقذ الكثير منا في بداية تعلمنا الاندرويد ومازال ينقذنا.
رابط الكلاسات المستخدمه في هذا الدرس:
- تم استخدام كلاس MyThreadPoolRunnable.java في كلاس Main4Activity.java.
المصادر والمراجع:
للمزيد راجع درس المقدمة.
نهاية الدرس
لاتنسى تتبع الدرس والدورة كذلك لإشعارك عندما يتم التعديل على المتحوى او اضافة المزيد من المعلومات. ايضاً لاتنسى الاعجاب بالدرس ومشاركته مع الاخرين.
محتوى الدورة
الكلمات الدليلية
عن الدرس
0 إعجاب |
1 متابع |
0 مشاركة |
1929 مشاهدات |
منذ 5 سنوات |
التعليقات (0)
لايوجد لديك حساب في عالم البرمجة؟
تحب تنضم لعالم البرمجة؟ وتنشئ عالمك الخاص، تنشر المقالات، الدورات، تشارك المبرمجين وتساعد الآخرين، اشترك الآن بخطوات يسيرة !