إحياء الكائن

في لغات البرمجة الموجهة للكائنات مع جمع البيانات المهملة ، يحدث إحياء الكائن عندما يصبح الكائن قابلاً للوصول (بمعنى آخر، لم يعد مهملاً) أثناء عملية تدمير الكائن ، كأثر جانبي لتنفيذ المُنهي .

تُسبب إعادة إحياء الكائنات عددًا من المشاكل ، لا سيما أن إمكانية إعادة إحياء الكائنات - حتى وإن لم تحدث - تجعل عملية جمع البيانات المهملة أكثر تعقيدًا وبطئًا بشكل ملحوظ، وهو سبب رئيسي لعدم تشجيع استخدام الدوال النهائية. تتعامل لغات البرمجة مع إعادة إحياء الكائنات بطرق متنوعة . في حالات نادرة، تُستخدم إعادة إحياء الكائنات لتنفيذ أنماط تصميم معينة، وخاصةً مجمع الكائنات ، [ 1 ] بينما في حالات أخرى، تُعد إعادة الإحياء خطأً غير مرغوب فيه ناتجًا عن خطأ في الدوال النهائية، وبشكل عام، يُنصح بتجنبها. [ 2 ]

عملية

تتم عملية إعادة إحياء الكائنات عبر الخطوات التالية: أولًا، يصبح الكائن غير قابل للوصول إليه من البرنامج، ويمكن جمعه (تدميره وإلغاء تخصيصه). ثم، أثناء تدمير الكائن، وقبل أن يقوم جامع البيانات المهملة بإلغاء تخصيصه، قد يتم تشغيل دالة إنهاء ، والتي بدورها قد تجعل هذا الكائن أو كائنًا آخر غير قابل للوصول إليه (يمكن الوصول إليه من الكائن الذي يحتوي على دالة إنهاء) قابلًا للوصول إليه مرة أخرى عن طريق إنشاء مراجع إليه، حيث قد تحتوي دالة الإنهاء على تعليمات برمجية عشوائية. إذا حدث هذا، فإن الكائن المشار إليه - والذي ليس بالضرورة الكائن الذي تم إنهاؤه - لم يعد غير قابل للوصول، ولا يمكن إلغاء تخصيصه، وإلا ستصبح المراجع إليه مراجع معلقة وتسبب أخطاء عند استخدامها، وعادةً ما تؤدي إلى تعطل البرنامج أو سلوك غير متوقع. بدلًا من ذلك، وللحفاظ على سلامة الذاكرة ، يتم إعادة الكائن إلى الحياة أو إحياؤه.

للكشف عن ذلك، يقوم جامع البيانات المهملة عادةً بعملية جمع على مرحلتين في وجود دوال الإنهاء: أولاً، يُنهي أي بيانات مهملة لها دالة إنهاء، ثم يُعيد فحص جميع البيانات المهملة (أو جميع البيانات المهملة التي يمكن الوصول إليها من الكائنات التي لها دوال إنهاء)، تحسباً لأن تكون دوال الإنهاء قد أعادت إحياء بعض البيانات المهملة. يُضيف هذا عبئاً إضافياً ويؤخر استعادة الذاكرة.

الأشياء المُعاد إحياؤها

قد يُعامل الكائن المُعاد إحياؤه معاملة الكائنات الأخرى، أو قد يُعامل معاملة خاصة. في العديد من لغات البرمجة، ولا سيما C# وJava وPython (ابتداءً من Python 3.4)، تُنهى الكائنات مرة واحدة فقط، لتجنب إمكانية إعادة إحيائها بشكل متكرر أو حتى كونها غير قابلة للتدمير؛ في C#، تُنهى الكائنات التي تحتوي على مُنهيات افتراضيًا مرة واحدة فقط، ولكن يمكن إعادة تسجيلها للإنهاء. في حالات أخرى، تُعتبر الكائنات المُعاد إحياؤها أخطاءً، ولا سيما في Objective-C؛ أو تُعامل معاملة الكائنات الأخرى، ولا سيما في Python قبل الإصدار 3.4.

يُطلق على الشيء الذي تم إحياؤه أحيانًا اسميُستخدم مصطلح "كائن زومبي" أو"زومبي" لوصف حالات مختلفة للكائنات المرتبطة بتدميرها، ويختلف الاستخدام باختلاف اللغة والمطور. لكن لمصطلح "كائن زومبي" معنى خاص في لغةObjective-C، سيتم توضيحه لاحقًا. تُشبه كائنات الزومبي إلى حد ماعمليات الزومبي، حيث أنها خضعت لتغيير في حالة الإنهاء وتقترب من تحرير الذاكرة، إلا أن التفاصيل تختلف اختلافًا كبيرًا.

المتغيرات

في إطار عمل .NET ، وتحديدًا في لغتي C# وVB.NET، يُشير مصطلح "إحياء الكائن" إلى حالة الكائن أثناء عملية الإنهاء: حيث يُعاد الكائن إلى حالته الأصلية (بعد أن كان غير قابل للوصول)، ثم يُنفَّذ مُنهي العملية، ثم يُعاد إلى حالته الأصلية غير القابلة للوصول (ولا يُسجَّل بعد ذلك للإنهاء مستقبلًا). في .NET، لا يتم تتبع الكائنات التي تحتاج إلى إنهاء كل كائن على حدة، بل تُخزَّن في "قائمة انتظار" الإنهاء، [ أ ] لذا، بدلًا من مفهوم الكائنات المُعاد إحياؤها بالمعنى المقصود في هذه المقالة، يُشار إلى الكائنات "المُدرجة في قائمة انتظار الإنهاء". علاوة على ذلك، يُمكن إعادة إدراج الكائنات في قائمة انتظار الإنهاء GC.ReRegisterForFinalize، مع الحرص على عدم تكرار إدراج الكائنات. [ ٢ ]

الآلية

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

يعيد نفسه إلى الحياة عن طريق إنشاء مرجع في كائن يمكنه الوصول إليه:

class Clingy : def __init__ ( self , ref = None ) -> None : self . ref = refdef __del __ ( self ) : if self.ref : self.ref.ref = self print ( " لا تتركني ! " )a = Clingy ( Clingy ( )) # إنشاء قائمة مرتبطة من عنصرين، # يُشار إليها بواسطة |a| a.ref.ref = a # إنشاء حلقة a.ref = None # مسح المرجع من العقدة الأولى # إلى الثانية يجعل الثانية غير صالحة a.ref = None

يعيد إحياء نفسه من خلال خلق مرجع في البيئة العالمية:

c = None class Immortal : def __del__ ( self ): global c c = self print ( "أنا لست ميتًا بعد." )c = Immortal () c = None # مسح |c| يجعل الكائن غير صالح c = None

في الأمثلة المذكورة أعلاه، في CPython قبل الإصدار 3.4، سيتم تشغيل عمليات الإنهاء بشكل متكرر، ولن يتم جمع الكائنات بواسطة جامع القمامة، بينما في CPython 3.4 والإصدارات الأحدث، سيتم استدعاء عمليات الإنهاء مرة واحدة فقط، وسيتم جمع الكائنات بواسطة جامع القمامة في المرة الثانية التي تصبح فيها غير قابلة للوصول.

مشاكل

تُسبب عملية إعادة إحياء الكائنات عددًا كبيرًا من المشاكل.

يُعقّد عملية جمع البيانات المهملة
إن إمكانية إعادة إحياء الكائنات تعني أنه يجب على جامع القمامة التحقق من الكائنات التي تم إحياؤها بعد الانتهاء - حتى لو لم يحدث ذلك بالفعل - مما يعقد عملية جمع القمامة ويبطئها.
أشياء غير قابلة للتدمير
في بعض الظروف، قد يكون الكائن غير قابل للتدمير: إذا تم إحياء كائن في مُنهيه الخاص (أو مجموعة من الكائنات التي تُحيي بعضها البعض نتيجة لمُنهياتها)، ويتم استدعاء المُنهي دائمًا عند تدمير الكائن، فلا يمكن تدمير الكائن ولا يمكن استعادة ذاكرته.
إحياء عرضي وتسريبات
ثالثًا، قد يكون إحياء الكائن غير مقصود، وقد يكون الكائن الناتج عبارة عن قمامة دلالية، وبالتالي لا يتم جمعه فعليًا، مما يتسبب في تسرب منطقي للذاكرة .
حالة غير متسقة وإعادة تهيئة
قد يكون الكائن المُعاد إحياؤه في حالة غير متسقة، أو قد ينتهك ثوابت الفئة ، نتيجةً لتنفيذ المُنهي الذي تسبب في حالة غير منتظمة. لذا، عادةً ما تحتاج الكائنات المُعاد إحياؤها إلى إعادة تهيئة يدوية. [ 1 ]
إنهاء أو إعادة إنهاء فريد
في بعض لغات البرمجة (مثل جافا وبايثون 3.4 والإصدارات الأحدث)، يُضمن تنفيذ عملية الإنهاء مرة واحدة فقط لكل كائن، لذا لن يتم استدعاء دوال الإنهاء الخاصة بالكائنات المُعاد إحياؤها؛ وبالتالي، يجب على هذه الكائنات تنفيذ أي تعليمات برمجية ضرورية للتنظيف خارج دالة الإنهاء. في لغات أخرى، يمكن للمبرمج فرض تنفيذ عملية الإنهاء بشكل متكرر؛ ومن أبرزها لغة C# GC.ReRegisterForFinalize. [ 1 ]

الحلول

لقد اعتمدت اللغات عدة طرق مختلفة للتعامل مع إعادة إنشاء الكائنات، وأكثرها شيوعًا هو وجود عملية جمع قمامة على مرحلتين في وجود أدوات الإنهاء، لمنع المراجع المعلقة؛ ومن خلال إنهاء الكائنات مرة واحدة فقط، وخاصة عن طريق وضع علامة على الكائنات بأنها قد تم إنهاؤها (عبر علامة)، لضمان إمكانية تدمير الكائنات.

لن تقوم جافا بتحرير الكائن حتى تثبت أنه غير قابل للوصول إليه مرة أخرى، ولكنها لن تُشغّل المُنهي أكثر من مرة. [ 3 ]

في بايثون، قبل الإصدار 3.4، كانت مكتبة CPython القياسية تتعامل مع الكائنات المُعاد إحياؤها بنفس طريقة تعاملها مع الكائنات الأخرى (التي لم يتم إنهاؤها مطلقًا)، مما يجعل الكائنات غير القابلة للتدمير ممكنة. [ 4 ] علاوة على ذلك، لم تكن المكتبة تجمع البيانات المهملة من الحلقات التي تحتوي على كائن له مُنهي، لتجنب المشاكل المحتملة المتعلقة بإعادة إحياء الكائنات. بدءًا من الإصدار 3.4، أصبح السلوك مشابهًا إلى حد كبير لسلوك جافا: [ ب ] يتم إنهاء الكائنات مرة واحدة فقط (حيث يتم وضع علامة "تم إنهاؤها بالفعل")، ويتم جمع البيانات المهملة من الحلقات على مرحلتين، حيث تتحقق المرحلة الثانية من الكائنات المُعاد إحياؤها. [ 5 ] [ 6 ]

سيضع الإصدار 2.0 من لغة Objective-C الكائنات المُستعادة في حالة "زومبي"، حيث تسجل جميع الرسائل المُرسلة إليها، لكنها لا تقوم بأي شيء آخر. [ 7 ] انظر أيضًا : العد التلقائي للمراجع: تصفير المراجع الضعيفة لمعالجة المراجع الضعيفة .

في إطار عمل .NET، وتحديدًا في لغتي C# وVB.NET، يتم تحديد إنهاء الكائن بواسطة "قائمة انتظار" الإنهاء، والتي يتم فحصها أثناء تدمير الكائن. تُوضع الكائنات التي تحتوي على مُنهي في هذه القائمة عند إنشائها، وتُزال منها عند استدعاء المُنهي، ولكن يمكن إزالتها يدويًا (قبل الإنهاء) SuppressFinalizeأو إضافتها مرة أخرى ReRegisterForFinalize. وبالتالي، بشكل افتراضي، يتم إنهاء الكائنات التي تحتوي على مُنهي مرة واحدة على الأكثر، ولكن يمكن تعطيل هذا الإنهاء، أو يمكن إنهاء الكائنات عدة مرات إذا تم إحياؤها (إتاحتها مرة أخرى) ثم إعادة إضافتها إلى قائمة انتظار الإنهاء. علاوة على ذلك، لا تتعقب المراجع الضعيفة، بشكل افتراضي، عملية الإحياء، مما يعني أن المرجع الضعيف لا يتم تحديثه إذا تم إحياء كائن؛ وتُسمى هذه المراجع بالمراجع الضعيفة القصيرة ، بينما تُسمى المراجع الضعيفة التي تتعقب عملية الإحياء بالمراجع الضعيفة الطويلة .

التطبيقات

يُعدّ إحياء الكائنات مفيدًا لإدارة مجموعة من الكائنات شائعة الاستخدام، ولكنه يُعقّد الشيفرة البرمجية ويجعلها أكثر تعقيدًا. [ 3 ] ينبغي استخدامه فقط مع الكائنات التي قد تُستخدم بكثرة والتي تستغرق عملية إنشائها/تدميرها وقتًا طويلًا. مثال على ذلك مصفوفة من الأرقام العشوائية، حيث يتم إنشاء عدد كبير منها وتدميره في وقت قصير، ولكن في الواقع لا يُستخدم منها إلا عدد قليل في الوقت نفسه. مع إحياء الكائنات، تُقلّل تقنية التجميع من العبء الزائد غير الضروري لعمليات الإنشاء والتدمير. هنا، يحصل مدير المجموعة على معلومات مكدس الكائنات على شكل مرجع للكائن، إذا كان سيتم تدميره حاليًا. سيحتفظ مدير المجموعة بالكائن لإعادة استخدامه لاحقًا. [ 9 ]

انظر أيضاً

ملحوظات

  1. 1 2 هذا ليس طابورًا بالمعنى الدقيق للكلمة، حيث يمكن إزالة العناصر من المنتصف بواسطةGC.SuppressFinalization.
  2. يستخدم CPython عدادات المراجع للنفايات غير الدورية، مع كاشف دورات منفصل، بينما تستخدم معظم تطبيقات Java جامع النفايات التتبعي.

مراجع

  1. 1 2 3 غولدشتاين، زورباليف وفلاتو 2012 ، ص. 129 . 
  2. 1 2 ريختر 2000 .
  3. ١ ٢ "ما هو الإحياء (في عملية جمع البيانات المهملة)؟" . XYZWS. مؤرشف من الأصل بتاريخ ٢٠١١-١١-٢٣ . تم الاسترجاع بتاريخ ٢٠١١-٠٨-٠١ . قد يتوقف الكائن الذي كان مؤهلاً لجمع البيانات المهملة عن كونه مؤهلاً ويعود إلى حالته الطبيعية. ضمن دالة finalize()، يمكنك تعيين قيمة this إلى متغير مرجعي ومنع جمع هذا الكائن، وهو إجراء يسميه العديد من المطورين الإحياء. /لا يتم استدعاء دالة finalize() أكثر من مرة بواسطة JVM لأي كائن معين. لن يستدعي JVM دالة finalize() مرة أخرى بعد الإحياء (لأن دالة finalize() قد تم تشغيلها بالفعل لهذا الكائن).
  4. إجابة تيم بيترز على سؤال " كم مرة يمكن استدعاء الدالة `__del__` لكل كائن في بايثون؟ "
  5. ما الجديد في بايثون 3.4 ، PEP 442: إنهاء الكائنات الآمن
  6. بيترو، أنطوان (2013). "PEP 442 -- إنهاء الكائن الآمن" .
  7. تنفيذ طريقة الإنهاء
  8. غولدشتاين، زورباليف وفلاتو 2012 ، ص 131 . 
  9. "إحياء الكائنات" (ملف PDF) . Hesab.net . تاريخ الاسترجاع: 1 أغسطس 2011. يُعد إحياء الكائنات تقنية متقدمة يُحتمل أن تكون مفيدة فقط في سيناريوهات غير اعتيادية، مثل عند تنفيذ مجموعة من الكائنات التي يستغرق إنشاؤها وتدميرها وقتًا طويلاً. ... يُظهر تطبيق ObjectPool التجريبي أن مدير مجموعة الكائنات يُمكنه تحسين الأداء عند إنشاء وتدمير العديد من الكائنات بشكل متكرر. لنفترض أن لديك فئة RandomArray، التي تُغلف مصفوفة من الأرقام العشوائية. يقوم البرنامج الرئيسي بإنشاء وتدمير آلاف كائنات RandomArray، على الرغم من أن عددًا قليلاً فقط من الكائنات يكون نشطًا في لحظة معينة. نظرًا لأن الفئة تُنشئ المصفوفة العشوائية في دالة البناء الخاصة بها (وهي عملية تستغرق وقتًا طويلاً)، فإن هذا الوضع مثالي لتقنية التجميع. ... النقطة الحاسمة في تقنية التجميع هي أن فئة PoolManager تحتوي على مرجع إلى الكائنات غير المستخدمة في المجموعة (في كائن PooledObjects Stack)، ولكن ليس إلى الكائنات التي يستخدمها البرنامج الرئيسي. في الواقع، لا تبقى هذه الكائنات حية إلا من خلال مراجع في البرنامج الرئيسي. عندما يُعيّن البرنامج الرئيسي كائن RandomArray إلى Nothing (أو يسمح له بالخروج من نطاق التعريف) ويحدث جمع البيانات المهملة، يستدعي جامع البيانات المهملة دالة Finalize الخاصة بالكائن. وبالتالي، يُتاح للكود الموجود داخل دالة Finalize الخاصة بـ RandomArray فرصة لإعادة إنشاء نفسه عن طريق تخزين مرجع إليه في بنية PooledObjects الخاصة بـ PoolManager. لذلك، عند استدعاء دالة NewRandomArray مرة أخرى، يمكن لكائن PoolManager إعادة كائن مجمع إلى العميل دون الحاجة إلى المرور بعملية إنشاء كائن جديد تستغرق وقتًا طويلاً.