آشنایی با مفهوم Garbage Collection در زبان برنامه‌نویسی پایتون

آشنایی با مفهوم Garbage Collection در زبان برنامه‌نویسی پایتون

هنگامی که یک آبجکت از روی کلاس خاصی در زبان برنامه‌نویسی پایتون ساخته می‌شود یا متغیر جدیدی در برنامه تعریف می‌گردد، مفسر پایتون بخشی از حافظۀ سیستم را به ذخیره‌سازی آبجکت مد نظر و یا آن متغیر جدید اختصاص می‌دهد و لازم به یادآوری است که حافظۀ یک سیستم علیرغم گنجایش بالایش، باز هم محدودیت‌هایی دارا است و از همین روی تعداد آبجکت‌ها و متغیرهایی که می‌توان در یک برنامه نمونه‌سازی کرد باید مدیریت شود چرا که اِشغال فضای سیستم توسط آبجکت‌ها و متغیرهای فراوان، به خصوص در اپلیکیشن‌های بزرگ و اینترپرایز، می‌تواند سیستم را با کمبود منابع مواجه ساخته و باعث بروز مشکل گردد.

در همین راستا، طراحان زبان پایتون قابلیتی را توسعه داده‌اند که در آن به صورت اتوماتیک بخشی از فضای حافظه به آبجکت تعریف‌شده اختصاص داده شده و یا از آن گرفته می‌شود مضاف بر اینکه دولوپرها می‌توانند در صورت لزوم و با به‌کارگیری برخی قابلیت‌های زبان برنامه‌نویسی پایتون دستوراتی را به منظور گرفتن حافظه از یکسری آبجکت مد نظر خود در اپلیکیشن مربوطه اِعمال کنند. به طور کلی، زبان پایتون از دو روش تحت عناوین Reference Counting و Garbage Collection جهت مدیریت حافظه در فرآیند توسعۀ اپلیکیشن استفاده می‌کند که در ادامه قصد داریم تا به بررسی نحوۀ عملکرد هر یک بپردازیم.

روش Reference Counting به منظور مدیریت حافظه

زبان برنامه‌نویسی پایتون تا نسخۀ 2.0 صرفاً از قابلیت‌های روش Reference Counting به منظور مدیریت حافظه استفاده می‌کرد که در این روش مفسر پایتون تعداد ارجاعات به هر یک از آبجکت‌های تعریف‌شده در برنامه را شمارش کرده و به محض صفر شدن تعداد رفرنس‌ها به آبجکت مد نظر، حافظۀ اختصاص‌یافته بدان را آزاد می‌کرد.

همان‌طور که پیش‌تر اشاره کردیم، قابلیت Reference Counting در پایتون به صورت اتوماتیک و لحظه‌ای تعداد ارجاعات به هر یک از آبجکت‌های تعریف‌شده در برنامه را شمارش کرده و آبجکت‌هایی که ارجاعی به آن‌ها داده نمی‌شود را از حافظه پاک می‌کند که برای درک بهتر این موضوع مثال زیر را مد نظر قرار می‌دهیم:

>>> b = 9
>>> b = 4

در کد فوق، متغیری تحت عنوان b تعریف کرده و عدد 9 از نوع دادۀ int (عدد صحیح) را بدان منتسب کرده‌ایم و بدین ترتیب متغیر b به محلی از حافظه اشاره می‌کند که عدد 9 در آن ذخیره شده است و از همین روی تعداد ارجاعات به عدد مذکور برابر با ۱ است. در ادامه، عدد 4 را به متغیری تحت عنوان b منتسب می‌کنیم که در چنین شرایطی متغیر b دیگر به عدد 9 اشاره نکرده بلکه به محل ذخیرۀ عدد 4 در حافظۀ سیستم اشاره می‌کند که در این صورت تعداد ارجاعات به عدد 9 برابر با 0 شده و از این پس فقط ۱ ارجاع به عدد 4 داریم.

در چنین شرایطی، گاربج کالکتور پایتون بر اساس الگوریتم Reference Counting عدد 9 را از حافظۀ سیستم پاک کرده و بالتبع حافظۀ اختصاص‌یافته بدان را آزاد می‌کند چرا که هیچ ارجاعی به آن صورت نمی‌گیرد. به طور کلی، می‌توان گفت که تعداد ارجاعات به مقادیر ذخیره‌شده در حافظۀ سیستم معمولاً در سه حالت افزایش می‌یابد که در ادامه هر یک را با ذکر مثالی کاربردی تشریح می‌کنیم.

- اول در صورتی که مقادیری از دیتا تایپ‌های مختلف را به متغیری منتسب کرده سپس آن را به متغیر دیگری منتسب کنیم، تعداد ارجاعات به مقدار مذکور افزایش می‌یابد که برای درک بهتر این موضوع کد زیر را در نظر می‌گیریم:

>>> listA = [1, 2, 3]
>>> moreList = listA

در کد فوق، متغیری از جنس لیست تحت عنوان listA تعریف کرده و سه مقدار عددی را به آن منتسب کرده‌ایم و بدین ترتیب متغیر listA به محلی از حافظه اشاره می‌کند که لیستی حاوی مقادیر ۱، ۲ و ۳ در آن ذخیره شده است و از همین روی تعداد ارجاعات به این لیست برابر با عدد 1 می‌باشد. در سطر بعد، متغیر listA را به متغیر دیگری به نام moreList منتسب کرده‌ایم که در چنین شرایطی متغیر moreList به محلی از حافظه اشاره می‌کند که متغیر listA به آن ارجاع می‌دهد و در این صورت می‌توان گفت که تعداد ارجاعات به لیست مذکور یک واحد افزایش پیدا می‌کند.

- دوم اینکه چنانچه متغیری را به عنوان آرگومان ورودی به فانکشنی پاس دهیم، تعداد ارجاعات به مقدار منتسب به متغیر مذکور افزایش می‌یابد که به عنوان یک مثال در این مورد کدی مانند زیر خواهیم داشت:

>>> import sys
>>> listA = []
>>> print(sys.getrefcount(listA))
2

در کد فوق، ابتدا ماژول sys را ایمپورت کرده و در ادامه آبجکتی از جنس لیست تحت عنوان listA تعریف کرده و لیستی بدون اِلِمان را به آن منتسب نموده‌ایم که بدین ترتیب متغیر listA به فضای ذخیره‌شدۀ لیستِ [ ] از حافظه اشاره می‌کند. در سطر بعد، فانکشنی از پیش تعریف‌شده تحت عنوان ()getrefcount از ماژول sys را فراخوانی کرده‌ایم و نحوۀ کار این فانکشن بدین صورت است که تعداد ارجاعاتِ آرگومان ورودی‌اش به حافظۀ سیستم را در خروجی ریترن می‌کند و همان‌طور که می‌بینید، متغیر listA را به عنوان آرگومان ورودی به این فانکشن داده‌ایم و در ادامه گفته‌ایم عدد ریترن‌شده توسط فانکشن ()print در خروجی چاپ شود.

بنابراین خروجی حاصل از فراخوانی فانکشن ()getrefcount با آرگومان ورودیِ listA برابر با عدد 2 می‌باشد بدین معنی که متغیر listA دو مرتبه به حافظۀ سیستم ارجاع داده است به طوری که یک مرتبه به لیستِ [ ] اشاره کرده و یک مرتبۀ دیگر به عنوان آرگومان ورودی به فانکشن ()getrefcount پاس داده می‌شود که این امر منجر به افزایش تعداد ارجاعات متغیر listA به حافظۀ سیستم می‌شود. همچنین به عنوان مثالی دیگر داریم:

>>> import sys
>>> def refFunction(numbers):
        print(sys.getrefcount(numbers))
>>> refFunction(listA)
4
>>> print(sys.getrefcount(listA))
2

همان‌طور که ملاحظه می‌شود، فانکشنی تحت عنوان ()refFunction با یک پارامتر ورودی به نام numbers تعریف کرده‌ایم که این وظیفه را دارا است تا در صورت فراخوانی، تعداد ارجاعات آرگومان ورودیِ فانکشن را با استفاده از فانکشن از پیش تعریف‌شدۀ ()getrefcount در خروجی چاپ کند که در این صورت با تعریف فانکشن ()refFunction یک به اصطلاح Call Stack به منظور ذخیرۀ دستورات فانکشن و فراخوانی مجدد آن تشکیل می‌شود.

در این اِستک (پشته) یک به اصطلاح Frame تشکیل شده و فانکشن ()refFunction با پارامتر ورودی numbers در آن نگهداری می‌شود. حال در صورت فراخوانی این فانکشن، مفسر پایتون با ارزیابی دستور سطر چهارم و رسیدن به آرگومان ورودی listA به محلی از حافظه اشاره می‌کند که متغیر مذکور بدان ارجاع می‌دهد سپس فریم دیگری در پشته ایجاد شده و پارامتر ورودی numbers آدرس مربوط به محلی از حافظه که آرگومان ورودی بدان اشاره می‌کند را ذخیره می‌نماید.

در ادامه، دستور داخلی فانکشن ()refFunction اجرا شده، و همان‌طور که پیش‌تر اشاره کردیم، اکنون متغیر numbers به فضایی از حافظه اشاره می‌کند که متغیر listA بدان ارجاع می‌دهد بنابراین در این صورت هم متغیر numbers و هم متغیر listA به فضای مربوط به لیستِ [ ] اشاره می‌کنند و می‌توان گفت که تا به اینجا تعداد ارجاعات به لیست [ ] برابر با عدد 3 می‌باشد که در ادامۀ اجرای دستورات داخلی فانکشن متغیر مد نظر مجدداً به عنوان آرگومان ورودی به فانکشن ()getrefcount داده می‌شود و بدین ترتیب تعداد ارجاعات به لیست مذکور به عدد 4 افزایش می‌یابد. از سوی دیگر، با اتمام فراخوانی فانکشن ()refFunction با آرگومان ورودی listA مفسر پایتون فریم اختصاص‌یافته به منظور اجرای فانکشن را از اِستک مذکور حذف می‌کند و از همین روی می‌بینیم که با فراخوانی مجدد فانکشن ()getrefcount با آرگومان ورودی listA تعداد ارجاعات این متغیر به حافظۀ سیستم به عدد 2 کاهش یافته است.

- سوم هم اینکه اضافه کردن متغیر مد نظر به آبجکتی از جنس لیست نیز منجر به افزایش تعداد ارجاعات بدان می‌شود. برای مثال، لیست زیر را مد نظر قرار می‌دهیم:

>>> listB = [listA, listA, listA]
>>> print(sys.getrefcount(listA))
5

اجرای کد فوق منجر به ریترن شدن عدد 5 در خروجی می‌گردد بدین صورت که متغیر listA به محل مربوط به ذخیرۀ لیست [ ] در حافظه اشاره می‌کند و از آنجایی که این متغیر را سه مرتبه در لیست دیگری تحت عنوان listC ذکر کرده‌ایم، هر یک از اِلِمان‌های این لیست نیز به تعداد یک مرتبه به محل ذخیرۀ لیست [ ] اشاره می‌کنند که در مجموع تعداد ارجاعات به لیست مذکور معادل عدد 4 می‌باشد به علاوه اینکه پاس دادن متغیر listA به عنوان آرگومان ورودی به فانکشن ()getrefcount منجر به افزایش تعداد ارجاعات به عدد 5 می‌گردد.

به عنوان یک مثال دیگر نیز می‌توان به آبجکت‌هایی از نوع دادۀ دیکشنری اشاره کرد که اضافه کردن متغیر به آن‌ها نیز منجر به افزایش تعداد ارجاعات متغیر مذکور به حافظۀ سیستم می‌شود. در همین راستا، در کد زیر قصد داریم تا متغیر مربوط به مثال قبل را به عنوان Value در آبجکتی با دیتا تایپ دیکشنری بیاوریم:

>>> dictA = {'key': listA}
>>> print(sys.getrefcount(listA))
6

در مثال فوق، آبجکتی از جنس دیکشنری متشکل از یک اِلِمان با کلیدی معادل با استرینگ «key» و مقداری معادلِ لیست منتسب به متغیر listA تعریف کرده و آن را به متغیر dictA اختصاص داده‌ایم که بدین ترتیب تعداد ارجاعات به فضایی از حافظه که متغیر listA بدان اشاره می‌کند، یک واحد افزایش می‌یابد.

آشنایی با مفهوم Reference Cycle

روش Reference Counting برخی معایبی دارا است که از آن جمله می‌توان به عدم توانایی در آزادسازی حافظه در شرایط به اصطلاح Reference Cycle اشاره کرد. Reference Cycle در آبجکت‌هایی همچون لیست، تاپل، دیکشنری و کلاس‌ها به وجود می‌آید که در آن آبجکت‌ها به صورت اصطلاحاً Cyclic (چرخه‌ای) به فضایی از حافظه که به آبجکتی دیگر اشاره می‌کنند ارجاع داده و در چنین شرایطی تعداد ارجاعات به فضای مد نظر از حافظه هرگز برابر 0 نشده و حداقل برابر با عدد 1 می‌باشد چرا که آبجکت‌های مذکور به خود و یا به دیگر آبجکت‌ها به صورت چرخه‌ای ارجاع می‌دهند. برای درک بهتر این موضوع، در ادامه نمونه‌ای ساده‌ از یک به اصطلاح Reference Cycle را آورده‌ایم:

>>> listB = [1, 2]
>>> listB.append(listB)

در کد فوق، آبجکتی از جنس لیست تحت عنوان listB تعریف کرده و هر یک از اعداد فوق را بدان اختصاص داده‌ايم و در ادامه با فراخوانی فانکشن از پیش تعریف‌شدۀ ()append قصد داریم تا آبجکت listB را به انتهای لیست مذکور اضافه کنیم که در این صورت تعداد ارجاعات متغیر listB به حافظۀ مربوط به لیست ذخیره‌شده در سیستم یک واحد افزایش می‌یابد بدین صورت که متغیر مذکور یک مرتبه به مکانی از حافظه اشاره می‌کند که لیستِ [2 ,1] در آن ذخیره شده است و یک مرتبۀ دیگر اِلِمان سوم از این لیست به صورت چرخه‌ای به مکانی از حافظه ارجاع می‌دهد که خود بدان اشاره می‌کند. در چنین شرایطی حذف متغیر listB صرفاً منجر به کاهش یک مرتبه‌ای تعداد ارجاعات به لیست ذخیره‌شده در حافظۀ سیستم می‌شود. حال در ادامه کد زیر را مد نظر قرار می‌دهیم:

>>> del listB

همان‌طور که می‌بینید، با به‌کارگیری کیورد del متغیر listB را حذف کرده‌ایم بدین معنی که متغیری تحت عنوان listB دیگر به لیستِ [2 ,1] ارجاع نمی‌دهد و از همین روی تعداد ارجاعات به آن یک مرتبه کاهش می‌یابد و این در حالی است که اِلِمان سوم از ليست مذکور به خود لیست نیز ارجاع می‌دهد به طوری که می‌توان گفت علیرغم حذف متغیر listB و عدم دسترسی به آن، لیستِ [2 ,1] تعداد یک مرتبه به فضایی از حافظه ارجاع می‌دهد که خود در آن ذخیره شده است.

حال به عنوان مثالی دیگر، شرایطی را در نظر می‌گیریم که در آن دو یا چند آبجکت از نوع دادۀ دیکشنری داریم که به صورت چرخه‌ای به یکدیگر اشاره می‌کنند که برای این منظور کدی مانند زیر خواهیم داشت:

>>> objectA = {}
>>> objectB = {}
>>> objectA['obj2'] = objectB
>>> objectB['obj1'] = objectA

در کد فوق، دو آبجکت از جنس ديكشنری تحت عناوين objectA و objectB تعريف كرده‌ایم و در ادامه اولین اِلِمان از دیکشنری objectA را بدین صورت تعریف کرده‌ایم که مقدار کلید آن برابر با استرینگ «obj2» و مقدارش را برابر با خود آبجکت دیکشنری objectB قرار داده‌ایم. در سطر بعد، به ازای اِلِمان اول دیکشنری objectB یک کلید به صورت استرینگ «obj1» تعریف کرده و مقدار متناظر با آن را برابر با خود آبجکت دیکشنری objectA قرار داده‌ایم و بدین ترتیب می‌توان گفت که اِلِمان اول مربوط به هر یک از آبجکت‌های دیکشنری objectA و objectB به صورت چرخه‌ای به یکدیگر اشاره می‌کنند به طوری که متغیرهای objectA و objectB همواره به هر یک از مقادیر منتسب به یکدیگر ارجاع می‌دهند که به منظور درک بهتر این موضوع، در ادامه چرخۀ ارجاعات دو آبجکت را تشریح می‌کنیم:

- ارجاع متغیر objectA به محلی از حافظه که دیکشنریِ { } در آن ذخیره شده است.
- ارجاع دیکشنریِ { } به فضایی از حافظه که متغیر objectB بدان اشاره می‌کند که در سطر بعد گفته‌ایم متغیر objectB به فضایی از حافظه اشاره کند که متغیر objectA بدان ارجاع می‌دهد (در واقع، همان‌طور که پیش‌تر اشاره کردیم، متغیرهای objectA و objectB همواره به صورت چرخه‌ای به یکدیگر ارجاع می‌دهند.)

حال اگر بخواهیم هر یک از متغیرهای objectA یا objectB را با به‌کارگیری کیورد del حذف کنیم، خواهیم داشت:

>>> del objectA

با اجرای دستور فوق، متغیر objectA حذف می‌شود بدین معنی که دیگر متغیری تحت عنوان objectA به فضایی از حافظه که دیکشنری { } در آن ذخیره شده ارجاع نمی‌دهد و بدین ترتیب تعداد ارجاعات به مقدار مذکور یک مرتبه کاهش می‌یابد در حالی که هنوز هم متغیر objectB بدان فضا از حافظه ارجاع می‌دهد.

با توجه به آنچه که در دو مثال پیشین بدان اشاره کردیم، مفسر پایتون در صورت مواجهه با آبجکت‌های به اصطلاح Reference Cycle هرگز قادر بر گرفتن حافظه از آبجکت مربوطه نخواهد بود چرا که همواره به میزان حداقل یک ارجاع بدان وجود خواهد داشت. در چنین شرایطی، روش Garbage Collection با به‌کارگیری قابلیت‌های ماژولی تحت عنوان gc که نامش برگرفته از کلمات Garbage Collector است، در زبان پایتون مورد استفاده قرار می‌گیرد که در آن می‌توان آبجکت‌هایی را از حافظۀ سیستم حذف کرد که بی‌دلیل بخشی از آن را به خود اختصاص داده‌اند اما از طریق سورس‌کد امکان دسترسی به چنین آبجکت‌هایی را نداریم که در ادامه قصد داریم تا با برخی از ویژگی‌های این ماژول آشنا شویم.

آشنایی با نحوۀ عملکرد ماژول gc

برای درک نحوۀ کار این ماژول می‌باید دو مفهوم کلی را تشریح کنیم که عبارتند از:

- Generation: این مفهوم به نحوۀ دسته‌بندی آبجکت‌های ذخیره‌شده در حافظه اشاره دارد و به طور کلی می‌توان گفت که آبجکت‌های تعریف‌شده در برنامه در سه سطح دسته‌بندی می‌شوند که عبارتند از سطوح صفر، یک و دو به طوری که ماژول gc همواره آبجکت‌های مذکور را مورد بررسی قرار داده و آبجکت‌های جدید تعریف‌شده را در دسته‌بندی سطح صفر قرار می‌دهد و آن دسته از آبجکت‌هایی که طی آخرین فرآیند گاربج کالکشن از حافظۀ سیستم حذف نمی‌شوند نیز در سطح یک دسته‌بندی می‌کند و به همین ترتیب آبجکت‌هایی از دسته‌بندی سطح یک که در آخرین گاربج کالکشن در حافظه باقی می‌مانند در دستۀ سطح دوم قرار می‌گیرند.

- Threshold: ماژول gc به ازای آبجکت‌های هر دسته‌بندی مقداری ماوسوم به Threshold (حدِ آستانه) تعریف می‌کند به طوری که در صورت افزایش تعداد آبجکت‌های هر دسته از مقدار این حد آستانه، گاربج کالکتور فراخوانی شده و آبجکت‌های مربوطه را از حافظۀ سیستم پاک می‌کند مضاف بر اینکه آبجکت‌های به جای مانده از پروسۀ گاربج کالکشن در دسته‌ای با یک سطح بالاتر از سطح جاری دسته‌بندی می‌شوند. 

در ادامۀ آموزش قصد داریم تا ببینیم که فرآیند اتوماتیک گاربج کالکشن با به‌کارگیری قابلیت‌های ماژول gc به چه نحوی انجام می‌گیرد.

آشنایی با فرآیند گاربج کالکشن Automatic 

همان‌طور که پیش‌تر اشاره کردیم، زبان برنامه‌نویسی پایتون از روشی تحت عنوان Reference Counting نیز به منظور آزادسازی فضای حافظه استفاده می‌کند اما این در حالی است که به منظور حذف مقادیری که به صورت چرخه‌ای به خود و یا سایر مقادیر ذخیره‌شده در حافظۀ سیستم ارجاع می‌دهند با مشکلاتی روبه‌رو بوده است و در همین راستا طراحان زبان پایتون ماژولی به نام gc را توسعه داده‌اند که در آن گاربج کالکتور با به‌کارگیری قابلیت‌های این ماژول، مشکل به اصطلاح Reference Cycle را مرتفع نموده است.

در ماژول gc، چنانچه تفاوت تعداد آبجکت‌های ذخیره‌شده با تعداد آبجکت‌های حذف‌شدۀ مربوط به برنامه به ازای هر دسته‌بندی بالاتر از مقدار تعریف‌شده برای Threshold آن دسته شود، گاربج کالکتور به صورت دوره‌ای فراخوانی شده و آبجکت‌های واجد شرایط را از حافظه پاک می‌کند. برای مثال، در کد زیر داریم:

>>> import gc
>>> gc.get_threshold()
(700, 10, 10)

همان‌طور که می‌بینید، ابتدا ماژول gc را ایمپورت کرده و در ادامه فانکشن ()get_threshold از این ماژول را فراخوانی کرده‌ایم که در نهایت مقادیر حد آستانه برای هر دسته از آبجکت‌های برنامه را ریترن می‌کند به طوری که هر یک از اعداد ریترن‌شده مقادیر پیش‌فرضی هستند که به عنوان حد آستانه برای آبجکت‌های هر دسته از برنامه در نظر گرفته شده‌اند و بدین ترتیب حد آستانۀ 700 برای آبجکت‌های جدیدِ تعریف‌شده در برنامه در نظر گرفته شده است و چنانچه تفاوت تعداد آبجکت‌های ذخیره‌شده در آن دسته با تعداد آبجکت‌های حذف‌شده از حافظۀ سیستم و مربوط به برنامۀ مذکور بالاتر از عدد 700 باشد، گاربج کالکتور به صورت اتوماتیک فراخوانی شده و آبجکت‌هایی را از حافظه پاک می‌کند که به صورت چرخه‌ای به خود و یا سایر آبجکت‌ها ارجاع می‌دهند اما از طریق برنامه امکان دسترسی به آن‌ها فراهم نمی‌باشد و به همین ترتیب برای آبجکت‌های دسته‌بندی مربوط به سطوح اول و دوم نیز حد آستانه به صورت پیش‌فرض برابر با عدد 10 در نظر گرفته شده است.

در ارتباط با نحوۀ کار الگوریتم تعریف‌شده در ماژول gc به منظور شناسایی آبجکت‌هایی که به صورت اصطلاحاً Cyclic به یکدیگر ارجاع می‌دهند، می‌توان گفت که الگوریتم مذکور آبجکت‌هایی همچون لیست، تاپل، دیکشنری و غیره را بررسی کرده و تمامی ارجاعات به هر یک از اِلِمان‌های آن‌ها را به صورت موقت حذف می‌کند سپس آبجکت‌هایی را از حافظۀ سیستم پاک می‌کند که تعداد ارجاعات به آن‌ها کمتر از عدد 2 بوده و همچنین امکان دسترسی به آبجکت‌های مذکور از سورس‌کد برنامه فراهم نباشد (متغیری که در سورس‌کد مربوطه به محل ذخیره‌شده مربوط به آبجکت مذکور اشاره نمی‌کند.)

همچنین با به‌کارگیری دستور زیر می‌توانیم تعداد هر یک از آبجکت‌های متعلق به دسته‌بندی‌های مختلف از برنامۀ خود را در خروجی مشاهده کنیم:

>>> import gc
>>> gc.get_count()
(986, 0, 0)

در کد فوق، فانکشن ()get_count از ماژول gc را فراخوانی کرده‌ایم که خروجی حاصل از این فراخوانی به ترتیب برابر با تعداد آبجکت‌های متعلق به دسته‌بندی مربوط به هر یک از سطوح صفر، یک و دو می‌باشد و همان‌طور که می‌بینید، در این مثال به تعداد 986 آبجکت در دستۀ سطح صفر داریم و هیچ آبجکتی متعلق به دو دسته‌بندی سطوح اول و دوم نداریم.

همچنین این ماژول دو فانکشن تحت عناوین ()disable و ()enable دارا است که به ترتیب به منظور غیرفعال‌سازی و فعال‌سازی فرآیند خودکار گاربج کالکشن در برنامه مورد استفاده قرار می‌گیرند که در کد زیر نحوۀ فراخوانی آن‌ها را می‌بینیم:

>>> import gc
>>> gc.disable()
>>> gc.enable()

همان‌طور که ملاحظه می‌شود، در ابتدا فرآیند اتوماتیک گاربج کالکشن را با فراخوانی متد مربوطه غیرفعال کرده‌ایم که در صورت نیاز می‌توانیم با اجرای دستور سطر سوم مجدداً آن را فعال نماییم. به علاوه، دولوپرها با به‌کارگیری برخی ویژگی‌های ماژول gc می‌توانند به صورت دستی نیز برخی آبجکت‌هایی را از حافظه پاک کنند که در ادامه نحوۀ انجام این کار را بررسی می‌کنیم.

نحوۀ انجام گاربج کالکشن به صورت Manual

ماژول gc قابلیت فراخوانی گاربج کالکتور را به صورت دستی برای دولوپرها فراهم کرده است تا بدین وسیله بتوانند در حین اجرای برنامه برخی آبجکت‌هایی که به صورت Cyclic به خود یا دیگر آبجکت‌ها ارجاع می‌دهند را از حافظۀ سیستم پاک کنند که برای این منظور نیاز است تا کدی مانند زیر را مد نظر قرار دهیم:

>>> import gc
>>> gc.collect()
577
>>> gc.get_count()
(409, 0, 0)

در کد فوق، فانکشن ()collect را از ماژول gc فراخوانی کرده‌ایم که این وظیفه را دارا است تا آبجکت‌های به اصطلاح Reference Cycle را شناسایی کرده و حافظۀ اختصاص‌یافته به آن‌ها را آزاد کند و در نهایت تعداد آبجکت‌های حذف‌شده را در خروجی ریترن کند.

همان‌طور که می‌بینیم، در نتیجۀ فراخوانی این فانکشن در مثال پیشین به تعداد 577 آبجکت از حافظۀ سیستم پاک شده است و از همین روی فراخوانی مجدد فانکشن ()get_count منجر به تولید عدد 409 برای آبجکت‌های دستۀ اول گردیده است. از سوی دیگر، با به‌کارگیری فانکشن ()set_threshold می‌توانیم تنظیمات پیش‌فرض مربوط به گاربج کالکتور را تغییر دهیم که برای مثال در کد زیر قصد داریم تا یکسری حدِ آستانۀ دلخواه به ازای هر یک از دسته‌بندی‌های سطوح مختلف برای آبجکت‌های تعریف‌شده در برنامه خود سِت کنیم:

>>> gc.set_threshold(1000, 15, 15)
>>> gc.get_threshold()
(1000, 15, 15)

همان‌طور که می‌بینیم، در کد فوق یکسری عدد دلخواه را به عنوان آرگومان ورودی به فانکشن ()set_threshold داده و بدین ترتیب حد آستانۀ مد نظر خود به ازای آبجکت‌های سطوح مختلف را تعریف کرده‌ایم و در نتیجه با فراخوانی مجدد فانکشن ()get_threshold می‌بینیم که اعداد مذکور به عنوان حد آستانه به منظور فراخوانی گاربج کالکتور در برنامه سِت شده‌اند.

به خاطر داشته باشید
افزایش حدود آستانه منجر بدین می‌گردد تا یکسری آبجکت به اصطلاح مُرده زمان بیشتری در حافظۀ سیستم نگهداری شوند.

به طور کلی، یک دولوپر تازه‌کار متوجه نمی‌شود که در یک فرآیند خودکار گاربج کالکشن چه زمانی آبجکتی از حافظۀ سیستم پاک می‌شود اما متد از پیش تعریف‌شده‌ای به نام __()del__ برای این منظور وجود دارد که به محض حذف یک آبجکت و پاک کردن آن از حافظۀ سیستم فراخوانی می‌شود که تحت عنوان Destructor نیز شناخته می‌شود. در واقع، متد __()del__ مسئول پاک کردن آبجکت‌هایی است که در برنامۀ مربوطه مورد استفاده قرار نمی‌گیرند و درست قبل از پاک کردن آبجکتی فراخوانی می‌شود که بدون استفاده مانده و بخشی از حافظۀ دستگاه را بی‌دلیل به خود اختصاص داده است.

در پایان لازم به یادآوری است که فراخوانی گاربج کالکتور به صورت دستی کاری پُرریسک بوده و به منظور بهبود فرآیند استفاده از آن می‌باید نکاتی را مد نظر قرار دهیم به طوری که هرگز گاربج کالکشن را به تعداد دفعات بسیار فراخوانی نکنیم چرا که علیرغم آزادسازی فضای حافظه باز هم محاسبات مورد نیاز به منظور تشخیص آبجکت‌های مناسب جهت حذف آن‌ها از حافظه بار زیادی روی سیستم از نظر اختصاص زمان و همچنین انجام محاسبات مورد نیاز به همراه دارد مضاف بر اینکه فراخوانی گاربج کالکتور به صورت دستی می‌باید حتماً پس از پایان اجرای اپلیکیشن و دوره‌ای انجام شود. در همین راستا، به منظور حفظ صحت آبجکت‌های تعریف‌شده در برنامۀ خود نیاز به مطالعه، تحقیق و بررسی بیشتر جهت تسلط کامل بر نحوۀ فراخوانی گاربج کالکتور به صورت دستی داریم.

کاربر میهمان

دوست گرامی شما به عنوان کاربر میهمان در سایت سکان آکادمی حضور دارید لطفاً برای ارسال دیدگاه ابتدا وارد حساب خود شوید

اگر login نکردی برامون ایمیلت رو بنویس: