
االسلام عليكــم ورحمـة الله وبركاتــه ،،
الدرس الثاني : العمليات على المسلك Thread Functions
ذكرنا في الدرس السابق ان الدالة CreateThread تعود بمقبض يمكن استخدامه لاحقا وقد اخذنا مثال على استخدام المقبض في الدالة WaitForSingleObject, وقلنا ان الـ Thread يمكن تشغيله مباشرة بعد إنشاءه وذلك باعطاء الوسيط الخامس للدالة CreateThread وهو وسيط تحديد Creation Flag اي " سمة إلانشاء " القيمة صفر 0 , ماذا لو اردنا ان يتم إنشاء المسلك Thread ويوقف " يعلق " عمله لحين استدعائه لاحقأ ؟ كل هذا سنتعرف عليه في هذا الدرس إن شاء الله .
وسنواصل ايظا في هذا الدرس استعراض باقي استخدامات المقبض الذي ترجع به الدالة CreateThread ولكن قبل ذلك لنلقى نظرة اشمل على دالة إنشاء الـ Thread.
:: تحويل نوع Casting دالة الثريد :-
كما تعلمنا سابقا ان الدالة CreateThread تقوم بانشاء مسلك جديد وترجع بمقبض لهذا المسلك الذي تم إنشائه وتعلمنا ان الدالة التي يستدعيها المسلك الجديد تكون من نوع البيانات DWORD WINAPI ولكن ماذا لو كانت الدالة التي سيبدأ منها المسلك من نوع اخر مثلا كأن تكون من النوع void او int فكيف سيتم استخدامها عندئذ؟
الحل بسيط ويكمن في تحويل نوع الدالة Casting باستخدام معامل التحويل (LPTHREAD_START_ROUTINE) امام اسم الدالة عند تمريره كوسيط لدالة الإنشاء .
لتوضيح ذلك لنفرض ان الـ Thread سيبدأ من الدالة الاتية :
void voidFunction(void)
{
cout << "This is a void Function" << endl;
}ويتم تمريرها الدالة السابقة voidFunctionالدالة إنشاء الثريد CreateThread كالاتي :
DWORD threadID; HANDLE ThreadhHandle; ThreadhHandle = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)voidFunction,0,0,&threadID);
:: انشاء الثريد في وضع التعليق Suspention :-
امر اخر اشرنا له سابقا وهو امكانية إنشاء المسلك وتعليق تنفيذه , ويتم بتمرير القيمة CREATE_SUSPENDED لسمة الإنشاء إي Creation Flagكالاتي :
DWORD threadID; HANDLE ThreadhHandle; ThreadhHandle = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)voidFunction,0,CREATE_SUSPENDED ,&threadID);
~~~ الدوال المرتبطة بالمسلك Thread Functions ~~~
:: تحديد اولوية Priority عمل الثريد :-
يتم تحديد اولوية الـ Thread باستخدام الدالة SupendThread وهذه الدالة تأخذ وسيط واحد وهو مقبض الثريد المراد تحديد اولويته والشكل العام لها كالاتي :
BOOL SetThreadPriority( HANDLE hThread, // handle to the thread int nPriority // thread priority level );
هذا الدالة تأخذ وسيطين , الوسيط الاول مقبض الثريد المرادتحديد اولويته , والوسيط الثاني قيمة int تمثل مستوى الاولوية و القيم الاتية تمثل مستويات الاولوية التي يمكن تمريرها للدالة.
THREAD_PRIORITY_LOWEST اولوية اقل بنقطتين من اولوية العملية Process
THREAD_PRIORITY_BELOW_NORMAL اولوية اقل بنقطة من اولوية العملية Process
THREAD_PRIORITY_NORMAL اولوية عادية
THREAD_PRIORITY_ABOVE_NORMAL اولوية اعلى بنقطة من اولوية العملية Process
THREAD_PRIORITY_HIGHEST اولوية اعلى بنقطتين من اولوية العملية Process
THREAD_PRIORITY_TIME_CRITICAL اولوية بمستوى 15
THREAD_PRIORITY_IDLE اولوية بمستوى 1 " شبه خاملة "
وفي حالة نجاح الدالة فإنها ترجع بقيمة لا تساوي صفر nonzero , وفي حالة فشلها فإنها ترجع بالقيمة 0 .
مثال على استخدامها مع مقبض الثريد الذي انشئناه في المثال السابق :
SetThreadPriority( ThreadhHandle , THREAD_PRIORITY_NORMAL );
:: الحصول على اولوية عمل الثريد :-
يمكن الحصول على مستوى اولوية الـ Thread في إي وقت باستخدام الدالة GetThreadPriority وهذه الدالة تأخذ وسيط واحد وهو مقبض الثريد المراد الحصول على مستوى اولويته والشكل العام لها كالاتي :
int GetThreadPriority( HANDLE hThread // handle to thread );
في حالة نجاح الدالة فإنها ترجع بقيمة تمثل مستوى اولية الـ Thread, وفي حالة فشلها فإنها ترجع بالقيمة THREAD_PRIORITY_ERROR_RETURN .
مثال على استخدامها مع مقبض الثريد الذي انشئناه في المثال السابق :
GetThreadPriority( ThreadhHandle );
:: الحصول على معرف الثريد :-
يمكن الحصول على معرف الـ Thread في إي وقت باستخدام الدالة GetCurrentThreadId وهذه الدالة ليس لها وسائط وسيط والشكل العام لها كالاتي :
DWORD GetCurrentThreadId(VOID);
وعند تنفيذ الدالة ترجع بقيمة من نوع البيانات DWORD تمثل معرف الـ Thread.
مثال على استخدامها مع مقبض الثريد الذي انشئناه في المثال السابق :
DWORD threadID; threadID = GetCurrentThreadId( ThreadhHandle );
:: ايقاف او تعليق Suspension عمل الثريد :-
يمكن ايقاف عمل الـ Thread في إي وقت باستخدام الدالة SupendThread وهذه الدالة تأخذ وسيط واحد وهو مقبض الثريد المراد تعليق تنفيذه والشكل العام لها كالاتي :
DWORD SuspendThread( HANDLE hThread // handle to the thread );
مثال على استخدامها مع مقبض الثريد الذي انشئناه في المثال السابق :
SuspendThread( ThreadhHandle );
:: أستئناف عمل الثريد :-
لإستئناف عمل الثريد يتم استخدام الدالة ResumeThread وهي دالة تأخذ وسيط واحد هو مقبض الثريد Threadوالشكل العام لها كالاتي :
DWORD ResumeThread( HANDLE hThread // identifies thread to restart );
هذه الدالة ترجع بقيمة suspend count او عدد تكرار ايقاف الثريد عموما ان كانت القيمة تساوي صفر 0 فالثريد لم يتم ايقافه واذا كانت تساوي 1 فالثريد كانت متوقف وتم استئنافه واذا كانت القيمة اكبر من 1 فالثريد لازال متوقف .
في حالة فشل الدالة فإنها ترجع بالقيمة 0xFFFFFFFF .
مثال على استخدامها مع مقبض الثريد الذي انشئناه في المثال السابق :
ResumeThread( ThreadhHandle );
:: انهاء عمل الثريد :-
لإنهاء اي ثريد Thread يجب توفر كود الانهاء ExitCode لذلك الثريد وهذا الكود من نوع DWORD ويتم الحصول عليه باستخدام الدالة GetExitCodeThread والشكل العام لها كالاتي :
BOOL GetExitCodeThread( HANDLE hThread, // handle to the thread LPDWORD lpExitCode // address to receive termination status );
هذا الدالة تأخذ وسيطين , الوسيط الاول مقبض الثريد المراد إنهائه والوسيط الثاني مؤشر لمتغيير يستقبل كود الإنهاء ExitCode .
وفي حالة نجاح الدالة ترجع بقيمة لا تساوي صفر nonzero , وفي حالة فشلها فإنها ترجع بالقيمة 0 .
بعد الحصول على كود الإنهاء ExitCode يتم إنهاء الثريد باستخدام احدى دالتين يوفرهما نظام التشغيل وهما الدالة ExitThread , وهي الافضل والآمن والدالة الاخرى هي TerminateThread وتعتبر اكثر خطورة و ينصح باستعمالها في اضيق الحدود .
:: اولا - الدالة ExitThread و الشكل العام لها كالاتي :
VOID ExitThread( DWORD dwExitCode // exit code for this thread );
هذا الدالة تأخذ وسيط واحد وهو كود الانهاء ExitCode الذي تم الحصول عليه باستخدام الدالة GetExitCodeThread التي اشرنا لها سابقا .
هذه الدالة هي المفضلة لإنهاء الثريد , فعند استدعائها تحرر ذاكرة الستاك Stack المخصصة للثريد وتقوم بإنهائه , ايظا تقوم بتغيير حالة الثريد من القيمة STILL_ACTIVE الى قيمة كود الإنهاء ExitCode , كما انها تصرح "Invoke" لدالة Entry-Point المستدعاة من مكتبات الربط الديناميكي (DLLs) التي يكون الـ ThreadمرتبطAttatched بها ويتم استدعائها بقيمة تشير لفك ارتباط Detach الثريد من مكتبة الربط الديناميكي .
ايظا ان كان الثريد اخر ثريد في العملية Process التي يتبع لها الثريد فأنها سوف تنهي العملية Process تللك .
مثال على استخدامها :
DWORD exCode; GetExitCodeThread( ThreadHandle , &exCode ); ExitThread( exCode );
:: ثانيا - الدالة TerminateThread والشكل العام لها كالإتي :
BOOL TerminateThread( HANDLE hThread, // handle to the thread DWORD dwExitCode // exit code for the thread );
هذه الدالة تأخذ وسيطين الوسيط الاول مقبض الثريد الـ Thread المراد إنهائه , والوسيط الثاني مؤشر لمتغير يستقبل قيمة كود الإنهاء ExitCode , وفي حالة نجاح الدالة ترجع بقيمة لا تساوي صفر nonzero , وفي حالة فشلها فإنها ترجع بالقيمة 0 .
عند استدعاء هذه الدالة فإنها تقوم بانهاء الـ Thread مباشرة دون تحرير ذاكرة الستاك Stack المخصصة للثريد , و تقوم بتغيير حالة الثريد من القيمة STILL_ACTIVE الى قيمة كود الإنهاء ExitCode , كما انها لا تعلم مكتبات الربط الديناميكة التي قد يكون الـ Thread مرتبط بها بحالة انهاء الثريد , وكما اشرنا سابقا تعتبر هذه الدالة خطرة ولا ينصح باستخدامها الا في اضيق الحدود.
ايظا ان كان الثريد اخر ثريد في العملية Process التي يتبع لها الثريد فأنها سوف تنهي العملية Process تللك .
مثال على استخدامها :
DWORD exCode; GetExitCodeThread( ThreadHandle,&exCode ); TerminateThread( ThreadHandle , exCode );
::: ملاحظة هامة جدا :::
إنهاء الـ Thread لا يعني ضرورة حذفه من ذاكرة نظام التشغيل , ولكن يتم حذف الـ Thread ككائن موجد في ذاكرة النظام عند غلق اخر مقبض Handle له .
لغلق مقبض Handle لكائن Object يتم استخدام الدالة CloseHandle والشكل العام لها كالاتي :
BOOL CloseHandle( HANDLE hObject // handle to object to close );
هذه الدالة تأخذ وسيط واحد هو مقبض الكائن المراد غلقه , وفي حالة نجاح الدالة ترجع بقيمة لا تساوي صفر nonzero , وفي حالة فشلها فإنها ترجع بالقيمة 0 .
مثال على استخدامها :
CloseHandle( ThreadHandle );
~~~ الخلاصة ~~~
إنهاء الـ Thread بطريقة صحيحة يتم في ثلاث خطوات هي :
1- الحصول على كود الانهاء باستخدام الدالة
2- إنهاء الـ Thread باحدى الدالتين او .
3- غلق مقبض الـ Thread باستخدام الدالة .
مثال على إنهاء الـ Thread بشكل صحيح :
DWORD exCode; GetExitCodeThread( ThreadHandle , &exCode ); ExitThread( exCode ); CloseHandle( ThreadHandle );
~~~ هل يجب إنهاء الـ Thread دائما ؟ ...
عند إنتهاء ورجوع الدالة التي يبدأ منها الـ Thread اي الدالة التي تمرر كوسيط لدالة الإنشاء " CreateThread " فإن نظام التشغيل Windows يستدعي تلقائيا الدالة ExitThread .
عموما يممكنا كما ذكرنا سابقا من انهاء الـ Thread في اي وقت .
~~~ ~~~ ~~~
هذه اهم الدوال المرتبطة بالثريد Thread وتوجد بضع دوال اخرى قليلة لم نتكلم عنها , ان دعت الحاجة ربما نتطرق لها لاحقا ان شاء الله تعالى .
اتمنى ان اكون افدتكم و لو بالقليل , مع اعتذر ي المسبق في حال وجود اي خطأ او تقصير, وللمشرفين صلاحية التعديل او التنسيق فضلا عن تصحيح اي خطأ يوجد .
والى دروس قادمة إن شاء الله تعالى اترككم في حفظ الله تعالى
والسلام عليكم و رحمة الله و بركاته
مفهوم تعدد المسالك في نظام التشغيل Windows - الدرس الاول :