هنگامی که یک آبجکت از روی کلاس خاصی در زبان برنامهنویسی پایتون ساخته میشود یا متغیر جدیدی در برنامه تعریف میگردد، مفسر پایتون بخشی از حافظۀ سیستم را به ذخیرهسازی آبجکت مد نظر و یا آن متغیر جدید اختصاص میدهد و لازم به یادآوری است که حافظۀ یک سیستم علیرغم گنجایش بالایش، باز هم محدودیتهایی دارا است و از همین روی تعداد آبجکتها و متغیرهایی که میتوان در یک برنامه نمونهسازی کرد باید مدیریت شود چرا که اِشغال فضای سیستم توسط آبجکتها و متغیرهای فراوان، به خصوص در اپلیکیشنهای بزرگ و اینترپرایز، میتواند سیستم را با کمبود منابع مواجه ساخته و باعث بروز مشکل گردد.
در همین راستا، طراحان زبان پایتون قابلیتی را توسعه دادهاند که در آن به صورت اتوماتیک بخشی از فضای حافظه به آبجکت تعریفشده اختصاص داده شده و یا از آن گرفته میشود مضاف بر اینکه دولوپرها میتوانند در صورت لزوم و با بهکارگیری برخی قابلیتهای زبان برنامهنویسی پایتون دستوراتی را به منظور گرفتن حافظه از یکسری آبجکت مد نظر خود در اپلیکیشن مربوطه اِعمال کنند. به طور کلی، زبان پایتون از دو روش تحت عناوین 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__
مسئول پاک کردن آبجکتهایی است که در برنامۀ مربوطه مورد استفاده قرار نمیگیرند و درست قبل از پاک کردن آبجکتی فراخوانی میشود که بدون استفاده مانده و بخشی از حافظۀ دستگاه را بیدلیل به خود اختصاص داده است.
در پایان لازم به یادآوری است که فراخوانی گاربج کالکتور به صورت دستی کاری پُرریسک بوده و به منظور بهبود فرآیند استفاده از آن میباید نکاتی را مد نظر قرار دهیم به طوری که هرگز گاربج کالکشن را به تعداد دفعات بسیار فراخوانی نکنیم چرا که علیرغم آزادسازی فضای حافظه باز هم محاسبات مورد نیاز به منظور تشخیص آبجکتهای مناسب جهت حذف آنها از حافظه بار زیادی روی سیستم از نظر اختصاص زمان و همچنین انجام محاسبات مورد نیاز به همراه دارد مضاف بر اینکه فراخوانی گاربج کالکتور به صورت دستی میباید حتماً پس از پایان اجرای اپلیکیشن و دورهای انجام شود. در همین راستا، به منظور حفظ صحت آبجکتهای تعریفشده در برنامۀ خود نیاز به مطالعه، تحقیق و بررسی بیشتر جهت تسلط کامل بر نحوۀ فراخوانی گاربج کالکتور به صورت دستی داریم.