گزاره defer در زبان برنامهنویسی Go – راهنمای کاربردی


گزاره defer در زبان برنامهنویسی Go یک روش آسان برای اجرای یک قطعه کد پیش از بازگشت تابع است. طبق مستندات Go تابعهای defer شده به جای اجرای بیدرنگ پیش از بازگشت تابع پیرامونی، به طور عکس خودشان به تأخیر میافتند. در ادامه مثالی از پیادهسازی LIFO را میبینید:
در ادامه به توضیح سازوکار داخلی این گزاره میپردازیم و سپس یک حالت پیچیدهتر را مطرح میکنیم.
پیادهسازی داخلی
محیط زمان اجرای Go اقدام به پیادهسازی LIFO با استفاده از یک لیست پیوندی میکند. در واقع یک struct تأخیر دار لینکی به struct بعدی که باید اجرا شود دارد:
زمانی که یک متد defer جدید ایجاد میشود، به Goroutine جاری وصل میشود و قبلی به متد فعلی لینک میشود چون تابع بعدی باید اجرا شود:
فراخوانیهای متوالی اینک بدون پشته هستند و تابعهای defer شده از بالا به صورت زیر هستند:
چنان که میبینیم ما تابعهای defer شده را در حلقه قرار نمیدهیم، بلکه یک به یک از پشته خارج میشوند. این رفتار از سوی کد ASM تولیدشده تأیید میشود:
متد deferproc دو بار فراخوانی میشود و به صورت درونی متد newdefer که قبلاً برای ثبت تابعمان به صوت متدهای defer شده دیدیم، فراخوانی میکند. در نهایت در پایان تابع، متد defer شده به لطف تابع deferreturn یک به یک فراخوانی میشود.
کتابخانه Go به ما نشان داد که struct به نام defer_ به خصوصیت panic *_panic_ نیز لینک شده است. در ادامه به بررسی فایده آن در طی یک مثال دیگر میپردازیم.
Defer و مقدار بازگشتی
تنها راه برای یک تابع defer شده جهت دسترسی به نتیجه بازگشتی استفاده از پارامتر نتیجه با نام است که در مشخصات چنین توصیف شده است:
اگر تابع defer شده یک function literal باشد و تابع پیرامونی دارای پارامترهای نتیجه با نام باشد که در دامنه literal قرار دارند، تابع defer شده میتواند به پارامترهای نتیجه پیش از بازگشت دسترسی داشته و آنها را تغییر دهد.
به مثال زیر توجه کنید:
زمانی که این رفتار درک شود میتوانیم آن را با تابع Recover ترکیب کنیم. در واقع Recover یک تابع درونی است که کنترل یک goroutine دارای panic را به دست میگیرد. Recover تنها درون تابعهای defer شده مفید است.
چنان که دیدیم struct به نام defer_ به خصوصیت panic_ لینک شده است و این کار در طی یک فراخوانی panic روی میدهد:
در واقع متد gopanic در حالت panic پیش از فراخوانی تابعهای defer شده فراخوانی میشود:
در ادامه یک مثال از تابع recover را میبینید که از مزیت پارامترهای نتیجه با نام استفاده میکند:
الحاق این دو به ما کمک میکند که به طرز مناسبی با تابع Recover کار کنیم و میتوانیم خطاهایمان را به فراخوانی کننده بازگشت دهیم. برای نتیجهگیری از این مقاله در مورد تابعهای defer شده نگاهی به بهبودهایی که برای ما فراهم میسازند میاندازیم.
بهبود عملکرد
آخرین نسخهای که کاربرد defer را بهبود بخشیده است، نسخه 1.8 Go است. ما با اجرای بنچمارک در کتابخانه Go (بین نسخههای 1.78 و 1.8) میتوانیم این بهبود را مشاهده کنیم:
این بهبود به لطف بهینهسازی روش تخصیص و جلوگیری از رشد پشته حاصل شده است. همچنین یک بهینهسازی برای گزاره defer بدون هیچ آرگومان وجود دارد که از یک کپی حافظه جلوگیری میکند. در ادامه بنچمارک یک تابع defer شده با/بدون آرگومان مقایسه شده است:
این کد هم اینک به لطف بهینهسازی دوم 10 درصد سریعتر شده است.
اگر این مطلب برای شما مفید بوده است، آموزشها زیر نیز به شما پیشنهاد میشوند:
- مجموعه آموزشهای برنامهنویسی
- مجموعه آموزشهای دروس علوم و مهندسی کامپیوتر
- زبان برنامه نویسی Go — راهنمای شروع به کار
- چرا باید زبان برنامه نویسی Go را بیاموزیم؟ — راهنمای جامع
- آموزش زبان برنامه نویسی Go: ساخت یک سرور چت به زبان ساده
==