ویرگول
ورودثبت نام
بهنام محمدزاده
بهنام محمدزادهبرنامه نویس ساده و تازه کار وب و کاربر لینوکس
بهنام محمدزاده
بهنام محمدزاده
خواندن ۲ دقیقه·۸ ماه پیش

بررسی عملکرد Asynchronous Preemption در زبان Go

اگر تو زبان Go عمیق شده باشین و سعی کرده باشین با نحوه عملکرد Scheduler آشنا بشین احتمالا Asynchronous Preemption به گوشتون خورده. تو این پست میخوام توضیح بدم که این اتفاق چطور میافته و نحوه عملکردش به چه شکله.

برای شروع شاید بد نباشه که بدونیم asynchronous preemption برای چی به وجود اومد و اصلا چه مشکلی رو حل میکنه؛ به همین منظور با یه مثال پیش میرم.

زمانی که GC میخواد اجرا بشه نیاز به (STW)Stop the World داره که تو این وضعیت باید همه گوروتین ها تو یه safe point متوقف بشن؛ کال شدن فانکشن یک safe point هست که گوروتین در این نقطه میتونه متوقف بشه تا GC کارش رو بدرستی انجام بده. ولی بعضی از گوروتین ها ممکنه موقع اجرا اصلا فانکشن کال نداشته باشن که به این حالت میگن tight loop.

کد پایین نمونه‌ای از tight loop هست:

تو این حالت گوروتین وقتی ۱۰ میلی ثانیه اجرا شد یه ترد به اسم sysmon که همیشه بصورت مستقل اجرا میشه میاد تشخیص میده که فلان گوروتین زیادی داره اجرا میشه و باید متوقف بشه تا نوبت به بقیه هم برسه و برای اینکه اون گوروتین رو متوقف کنه یه سیگنال SIGURG میفرسته.

از اونطرف یه گوروتین به اسم gSignal که هر ترد(M) یکی مخصوص خودش رو داره و میاد این سیگنال‌ها رو دریافت و هندل می‌کنه. در تصویر پایین هم میتونید ساختار M رو ببینید.

وقتی gSignal میبینه سیگنال دریافتی از نوع SIGURG هست متوجه میشه که باید preemption اتفاق بیافته و میاد چک میکنه که آیا این اتفاق باید بیافته یا نه؟ (از لینک پایین هم میتونید این فانکشن رو ببینید)

https://github.com/golang/go/blob/391dde29a37f3fd450f7d61e3f220930e0164b89/src/runtime/preempt.go#L342

بعدش میاد چک کنه ببینه که اگه preempt کنیم مشکلی پیش میاد یا نه؟ پس این فانکشن رو کال میکنه

https://github.com/golang/go/blob/391dde29a37f3fd450f7d61e3f220930e0164b89/src/runtime/preempt.go#L363

دلیلش هم اینه که ممکنه این گوروتین در حال کال کردن بعضی از فانکشن های runtime باشه که نباید وسط اجرای اون فانکشن ها preemption اتفاق بیافته؛ و همچنین چک میکنه ببینه stack فضای کافی داره یا نه(چون مرحله بعد بهش نیاز داره).

حالا که به یه safe point رسیدیم میاد و کار خفن اصلی رو انجام میده.


همونطور که قبلا دیدیم tight loop هیچ فانکشن کالی نداره! پس چجوری باید گوروتین رو مجبور به اینکار کرد؟

جواب، پوش کردن یک function call instruction به stack frame و تغییر PC هست! 🤯

این فانکشن این کار رو انجام میده:

https://github.com/golang/go/blob/391dde29a37f3fd450f7d61e3f220930e0164b89/src/runtime/signal_amd64.go#L80

این فانکشن اول میاد برای یک instruction جدید داخل stack frame جا باز میکنه و بعد رجیسترهای RSP و RIP رو دستکاری میکنه تا PC به asyncPreempt تغییر کنه و بعد از اینکه اون اجرا شد کد قبلی بطور نرمال مثل گذشته به کارش ادامه بده.



گولنگگوgogolang
۳
۰
بهنام محمدزاده
بهنام محمدزاده
برنامه نویس ساده و تازه کار وب و کاربر لینوکس
شاید از این پست‌ها خوشتان بیاید