فئة الموضوع في دلفي
رابتور [الاستوديو العقلي]
http://mental.mentu.com
الجزء 2
الأول هو المنشئ:
المنشئ TThread.Create(CreateSuspending: Boolean);
يبدأ
إنشاء موروث؛
AddThread;
FSuspended := CreateSuspending;
FCreateSuspened := CreateSuspending;
FHandle := BeginThread(nil, 0, @ThreadPRoc, Pointer(Self), CREATE_SUSPENDED, FThreadID);
إذا كان FHandle = 0 إذن
رفع EThread.CreateResFmt(@SThreadCreateError, [SysErrorMessage(GetLastError)]);
نهاية؛
على الرغم من أن هذا المنشئ لا يحتوي على الكثير من التعليمات البرمجية، إلا أنه يمكن اعتباره العضو الأكثر أهمية لأنه يتم إنشاء مؤشر الترابط هنا.
بعد استدعاء TObject.Create من خلال Inherited، الجملة الأولى هي استدعاء العملية: AddThread، ويكون كود المصدر الخاص بها كما يلي:
الإجراء AddThread؛
يبدأ
InterlockedIncrement(ThreadCount);
نهاية؛
يوجد أيضًا RemoveThread المطابق:
الإجراء RemoveThread؛
يبدأ
InterlockedDecrement(ThreadCount);
نهاية؛
وظيفتها بسيطة جدًا، وهي حساب عدد سلاسل العمليات في العملية عن طريق زيادة أو تقليل المتغير العام. الأمر فقط هو أن عملية Inc/Dec شائعة الاستخدام لا تُستخدم لزيادة أو تقليل المتغيرات هنا، ولكن يتم استخدام زوج من عمليات InterlockedIncrement/InterlockedDecrement، حيث تقومان بتنفيذ نفس الوظيفة تمامًا، سواء بإضافة أو طرح واحدة إلى المتغير. لكن هناك اختلافًا كبيرًا بينهما، وهو أن InterlockedIncrement/InterlockedDecrement آمن لمؤشر الترابط. أي أنه يمكنهم ضمان نتائج التنفيذ الصحيحة في ظل مؤشرات ترابط متعددة، لكن Inc/Dec لا يمكنها ذلك. أو في مصطلحات نظرية نظام التشغيل، يعد هذا زوجًا من العمليات "البدائية".
خذ زائد واحد كمثال لتوضيح الفرق في تفاصيل التنفيذ بين الاثنين:
وبشكل عام فإن عملية إضافة واحدة إلى بيانات الذاكرة تمر بثلاث خطوات بعد التحلل:
1. قراءة البيانات من الذاكرة
2. البيانات زائد واحد
3. تخزينها في الذاكرة
افترض الآن الموقف الذي قد يحدث عند استخدام Inc لإجراء عملية زيادة في تطبيق ذي خيطين:
1. يقرأ الخيط A البيانات من الذاكرة (من المفترض أن يكون 3)
2. يقرأ الخيط B البيانات من الذاكرة (أيضًا 3)
3. يضيف الخيط A واحدًا إلى البيانات (الآن هو 4)
4. يضيف الخيط B واحدًا إلى البيانات (الآن هو أيضًا 4)
5. يقوم الخيط A بتخزين البيانات في الذاكرة (البيانات الموجودة في الذاكرة الآن هي 4)
6. يقوم الخيط B أيضًا بتخزين البيانات في الذاكرة (البيانات الموجودة في الذاكرة لا تزال 4، لكن كلا الخيطين أضافا واحدًا إليها، والذي يجب أن يكون 5، لذلك هناك نتيجة غير صحيحة هنا)
لا توجد مشكلة من هذا القبيل في عملية InterlockIncrement، لأن ما يسمى بـ "البدائي" هو عملية غير منقطعة، أي أن نظام التشغيل يمكن أن يضمن عدم حدوث تبديل الخيط قبل تنفيذ "البدائي". لذلك في المثال أعلاه، فقط بعد انتهاء الخيط A من تنفيذ البيانات وتخزينها في الذاكرة، يمكن للخيط B البدء في جلب الرقم وإضافة رقم، وهذا يضمن أنه حتى في حالة متعددة الخيوط، ستكون النتيجة هي نفسها صحيح.
يوضح المثال السابق أيضًا حالة "تعارض الوصول إلى سلاسل المحادثات"، ولهذا السبب تحتاج سلاسل الرسائل إلى "المزامنة" (المزامنة). سيتم مناقشة ذلك بالتفصيل لاحقًا عند ذكر المزامنة.
عند الحديث عن التزامن، هناك استطراد: اعترض لي مينغ، الأستاذ بجامعة واترلو في كندا، ذات مرة على ترجمة كلمة "مزامنة" على أنها "مزامنة" في "مزامنة الخيوط"، وأعتقد شخصيًا أن ما قاله هو في الواقع أمر بالغ الأهمية معقول. تعني كلمة "التزامن" باللغة الصينية "الحدوث في نفس الوقت"، والغرض من "مزامنة الخيط" هو تجنب حدوث هذا "في نفس الوقت". في اللغة الإنجليزية، التزامن له معنيان: الأول هو التزامن بالمعنى التقليدي (يحدث في نفس الوقت)، والآخر هو "العمل في انسجام" (العمل في انسجام). يجب أن تشير كلمة "مزامنة" في "مزامنة سلسلة المحادثات" إلى المعنى الأخير، أي "للتأكد من أن الخيوط المتعددة تحافظ على التنسيق وتجنب الأخطاء عند الوصول إلى نفس البيانات". ومع ذلك، لا يزال هناك العديد من الكلمات المترجمة بشكل غير دقيق مثل هذه في صناعة تكنولوجيا المعلومات، نظرًا لأنها أصبحت اتفاقية، سأستمر في استخدامها في هذه المقالة، لأن تطوير البرمجيات عمل دقيق، وما الذي يجب توضيحه يجب ألا تكون أبدًا ولا يمكن أن تكون غامضة.
دعنا نعود إلى منشئ TThread والشيء الأكثر أهمية بعد ذلك هو هذه الجملة:
FHandle := BeginThread(nil, 0, @ThreadProc, Pointer(Self), CREATE_SUSPENDED, FThreadID);
يتم هنا استخدام دالة Delphi RTL BeginThread المذكورة سابقًا ولها العديد من المعلمات، أهمها المعلمتان الثالثة والرابعة. المعلمة الثالثة هي وظيفة الخيط المذكورة سابقًا، أي جزء التعليمات البرمجية الذي يتم تنفيذه في الخيط. المعلمة الرابعة هي المعلمة التي تم تمريرها إلى وظيفة مؤشر الترابط، وهنا هو كائن مؤشر الترابط الذي تم إنشاؤه (أي الذات). من بين المعلمات الأخرى، يتم استخدام الخامس لضبط الخيط للتعليق بعد الإنشاء وعدم التنفيذ على الفور (يتم تحديد عمل بدء الخيط وفقًا لعلامة CreateSuspending في AfterConstruction)، والسادس هو إرجاع معرف الخيط.
الآن دعونا نلقي نظرة على جوهر TThread: وظيفة الخيط ThreadProc. المثير للاهتمام هو أن جوهر فئة الخيط هذه ليس عضوًا في الخيط، ولكنه وظيفة عامة (لأن اصطلاح المعلمة لعملية BeginThread يمكنه استخدام الوظائف العامة فقط). هنا هو الكود الخاص به:
وظيفة ThreadProc(الموضوع: TThread): عدد صحيح؛
فار
FreeThread: منطقية؛
يبدأ
يحاول
إن لم يكن Thread.Terminated بعد ذلك
يحاول
Thread.Execute;
يستثني
Thread.FFatalException := AcquireExceptionObject;
نهاية؛
أخيراً
FreeThread := Thread.FFreeOnTerminate;
النتيجة := Thread.FReturnValue;
Thread.DoTerminate;
Thread.FFinished := صحيح؛
this.SignalSyncEvent;
إذا FreeThread ثم Thread.Free؛
EndThread(Result);
نهاية؛
نهاية؛
على الرغم من عدم وجود الكثير من التعليمات البرمجية، إلا أنها الجزء الأكثر أهمية في TThread بأكمله، لأن هذا الرمز هو الكود الذي يتم تنفيذه بالفعل في الموضوع. فيما يلي وصف سطرًا تلو الآخر للكود:
أولاً، حدد العلامة المنتهية لفئة مؤشر الترابط، إذا لم يتم وضع علامة عليها على أنها منتهية، فاستدعاء طريقة التنفيذ لفئة مؤشر الترابط لتنفيذ رمز مؤشر الترابط. نظرًا لأن TThread هي فئة مجردة وطريقة التنفيذ هي طريقة مجردة، فهي في الأساس ينفذ تنفيذ التعليمات البرمجية في الفئة المشتقة.
لذلك، فإن التنفيذ هو وظيفة مؤشر الترابط في فئة مؤشر الترابط. يجب اعتبار جميع التعليمات البرمجية في التنفيذ بمثابة رمز مؤشر ترابط، مثل منع تعارض الوصول.
في حالة حدوث استثناء في التنفيذ، يتم الحصول على كائن الاستثناء من خلال AcquireExceptionObject وتخزينه في عضو FFatalException من فئة مؤشر الترابط.
وأخيرا، هناك بعض اللمسات الأخيرة قبل انتهاء الخيط. يسجل المتغير المحلي FreeThread إعداد سمة FreeOnTerminated لفئة مؤشر الترابط، ثم يقوم بتعيين قيمة إرجاع مؤشر الترابط إلى قيمة سمة قيمة الإرجاع لفئة مؤشر الترابط. ثم قم بتنفيذ طريقة DoTerminate لفئة مؤشر الترابط.
رمز طريقة DoTerminate هو كما يلي:
الإجراء TThread.DoTerminate؛
يبدأ
إذا تم تعيينه (FOnTerminate) ثم قم بالمزامنة (CallOnTerminate)؛
نهاية؛
الأمر بسيط جدًا، ما عليك سوى استدعاء طريقة CallOnTerminate من خلال Synchronize، ويكون رمز طريقة CallOnTerminate كما يلي، وهو استدعاء حدث OnTerminate ببساطة:
الإجراء TThread.CallOnTerminate؛
يبدأ
إذا تم تعيينه (FOnTerminate) ثم FOnTerminate (Self)؛
نهاية؛
نظرًا لأن حدث OnTerminate يتم تنفيذه في المزامنة، فهو في الأساس ليس رمز مؤشر الترابط، ولكنه رمز مؤشر الترابط الرئيسي (راجع تحليل المزامنة لاحقًا للحصول على التفاصيل).
بعد تنفيذ OnTerminate، قم بتعيين علامة FFinished لفئة مؤشر الترابط على True.
بعد ذلك، يتم تنفيذ عملية SignalSyncEvent، ويكون رمزها كما يلي:
الإجراء SignalSyncEvent؛
يبدأ
SetEvent(SyncEvent);
نهاية؛
كما أنه بسيط جدًا، ما عليك سوى تعيين حدث عالمي: SyncEvent. ستشرح هذه المقالة استخدام الحدث بالتفصيل لاحقًا، وسيتم شرح الغرض من SyncEvent في عملية WaitFor.
ثم يتم تحديد ما إذا كان سيتم تحرير فئة مؤشر الترابط بناءً على إعداد FreeOnTerminate المحفوظ في FreeThread. عند تحرير فئة مؤشر الترابط، هناك بعض العمليات، راجع تطبيق المدمر التالي للحصول على التفاصيل.
أخيرًا، يتم استدعاء EndThread لإنهاء مؤشر الترابط ويتم إرجاع قيمة إرجاع مؤشر الترابط.
عند هذه النقطة، يكون الموضوع قد انتهى تماما.
(يتبع)