سینا قادری
سینا قادری
خواندن ۶ دقیقه·۳ سال پیش

مدل M-P-G گولنگ: الگوریتم زمان بند رانتایم چطور کار میکند


این پُست از وبلاگ توسعه دهندگان blog.snix.ir کپی برداری شده است

خب امروز توی یکی از گروه های گولنگ دیدم در مورد عملکرد گوروتین ها و تفاوت هاشون با ترد یا نخ های سیستم عامل بحثی مطرح شده بود که تصمیم گرفتم یه بار واسه همیشه این پست رو اوکی کنم تا ابهامات در این مورد برطرف بشه.. توی این پست در وحله اول تفاوت های thread های هسته با goroutine ها رو برسی میکنیم و بعد از اون مدل زمان بند MPG رو.. خب کارمون رو از پایه شروع میکنیم قبل از اون اگه درس سیستم عامل رو پاس کرده باشید (با یه استاد درست حسابی) قائدتا این چیزایی که قراره توضیح بدیم رو بهتر متوجه میشید. البته نگران نباشید، من همه تلاشم رو میکنم که به زبون ساده این موارد رو مطرح کنم.

اوکی برگردیم به خونه اول: مفهوم ترد یا نخ یا thread چیه؟ نخ ها به زبون ساده متون اجرایی هستند که شامل اطلاعاتی میشه که یه cpu واسه اجرای یک سری از دستورالعمل ها بهشون نیاز داره.. فرض کنید دارید یه کتابی رو میخونید، اما لازمه که یه استراحتی بکنید. برای اینکه بتونید بعدا خوندن این کتاب رو ادامه بدید لازمه که شماره صفحه و خط و کلمه ای که تا اینجای کار خوندید رو یادتون باشه. پس متون اجرایی یا به اصطلاح execution context این کتاب میشه شماره صفحه، شماره خط و شماره کلمه.. اگه یه همکلاسی داشته باشید که از همین روش واسه خوندن این کتاب استفاده کنه، پس وقتی که شما دارید استراحت میکنید این همکلاسی شما میتونه بدون اینکه مشکلی برای شما پیش بیاد از کتاب استفاده کنه.. مفهوم thread ها هم دقیقا همینه.. یک cpu (وقتی میگیم یک cpu یعنی یک واحد پردازشی نه یه cpu با ۲۰ تا هسته.. منظورمون یکی از هسته هاشه) با استفاده از همین روش و اجرای یه مقدار از دستورالعمل های این thread ها در بازه زمانی خیلی کم، توهم اجرای همزمان همه task ها در یک زمان رو ایجاد میکنه، درصورتی که اینطور نیست. در یک زمان فقط یک نخ میتونه از cpu استفاده کنه یا به اصطلاح به سی پی یو dispatch بشه.. اما cpu باید یادش باشه که کار فلان thread رو تا کجا ادامه داده که واسه دفعه بعدی از همونجا شروع به پردازش کنه.. به ذخیره کردن این اطلاعات و رفتن به سراغ thread بعدی میگن تعویض متن یا context switching.. خیلی ساده هست نه؟ با این روش ساده نخ ها میتونن در مدت زمان های بسیار کوتاه به سی پی یو dispatch و پردازش بشن جوری که انگار همه با هم در حال اجرا شدن هستن (همروندی)

اما thread با پروسه چه فرقی میکنه؟ به طور خلاصه پروسه ها شامل حداقل یک و حداکثر X نخ میشن.. نخ های یک پروسه همه با هم یک فضای حافظه مجازی رو به اشتراک میزارن.. تعویض متن کوتاه تر و ساده تری نسبت به پروسه ها دارن و ارتباط دادنشون باهم ساده هست.. البته نخ ها استک اختصاصی خودشون رو هم دارن که توی اون فضای حافظه اشتراکی ایجاد میشه.. ما برای thread ها و زمان بند های هسته لینوکس یه پست دیگه اوکی کردیم که قبل از این پست پیشنهاد میشه یه نگاهی هم به اون بندازید..

زمان بند چیه؟ خب گفتیم که نخ ها در مدت زمان کوتاهی به سی پی یو dispatch و پردازش میشن.. اما بر اساس چه ترتیبی؟ کدوم نخ اولویت بیشتری داره؟ کدوم نخ باید مدت زمان بیشتری پردازش بشه؟ و ... همه این سوالات رو زمان بند پاسخ میده، به طور خلاصه کارش مدیریت این نخ ها و dispatch کردنشون به cpu هست..

خب بریم سراغ بحث اصلی: تفاوت نخ های هسته و نخ های سطح کاربر یا همون goroutine ها توی go چیه؟

نخ های سطح هسته:

  • توسط هسته سیستم عامل و زمان بند هاش مدیریت میشن
  • تعویض متن طولانی تری دارن
  • مقدار پیش فرض حافظه ای (استک) که بهشون اختصاص داده میشه حدودا یک مگابایت یا بیشتر هست
  • تعداد بالای این نخ ها برای هر پروسه ممکنه مشکل ساز بشه.. (نمونش وب سرور اپاچی توی حالت fork)

نخ های سطح کاربر:

  • توسط رانتایم در سطح یوزر و توسط خود پروسه مدیریت میشن
  • سیستم عامل ازشون خبر دار نمیشه
  • تعویض متن کم هزینه و بهینه تری نسبت به نخ های هسته دارن
  • حافظه مورد نیازی (استک) در حدود چند کیلوبایت برای شروع لازم دارن و در صورت نیاز میتونه بیشتر بشه
  • با مکانیزم هایی شبیه به epoll در لینوکس یا kqueue در bsd ها برای io بلاک میشن
  • توی golang بهشون گوروتین میگن البته با greenthread یا coreroutine ها هم شناخته میشن.. اما گوروتین ها یکم خاص تر هستند و تفاوت هایی دارن
  • میتونیم چندین میلیون از این نخ ها رو ایجاد کنیم بدون اینکه مشکلی پیش بیاد
  • توی گولنگ خصوصا: استفاده ازشون آسون تره و زمانبندی منعطف تر هست
  • گوروتین ها توسط زمان بند رانتایم به thread های سیستم عامل یا هسته map میشن

خیلی خب تا اینجا فهمیدیم که تفاوت نخ های سیستم عامل و goroutine ها چیا هستن، از این به بعد الگورتیم زمان بندی M-P-G رانتایم گولنگ رو بررسی میکنیم و خواهیم فهمید که goroutine ها چطور توسط رانتایم گولنگ مدیریت و اجرا میشن...

همونطور که از اسم این مدل زمانبندی مشخص هست شامل سه استراکت M و P و G میشه.. این سه حرف مخفف:

  • حرف G: نشون دهنده یک گوروتین هست و شامل فیلد های ضروری مورد نیاز میشه مثله فیلد های current state و stack trace.. همچنین یه فیلد دیگه هم داره که رفرنسی هست به کدی که این گوروتین اجراش میکنه..
  • حرف P: مخفف proccess هست.. در واقع GOMAXPROCS مشخص کننده تعداد این بخش هست.. لازمه بدونید قبل از go1.5 این مقدار همیشه ۱ میبود اما در ورژن های جدید تر گولنگ این مقدار تعداد هسته های cpu سخت افزار هست.. مقدار این متغیر قابل تغییر هست با استفاده از runtime.GOMAXPROCS(n int) میتونید تعداد این متغیر رو تغییر بدید.. افزایش تعداد P ها شاید برای سرویس های io bound فکر خوبی باشه.. اما جدای از اینها proccess یعنی چی؟ به طور خلاصه این proccess یک broker هست. یعنی کارش اینه که گوروتین ها رو به نخ های سطح هسته dispatch کنه، به زبون دیگه گوروتین ها رو به نخ های هسته map کنه.. یادمون باشه که گوروتین ها بدون اینکه به نخ های سطح هسته map بشن، به خودی خود واقعا کاری نمیتونن بکنن. این استراکت روی heap الوک میشه و شامل یک صف fifo میشه که بهش local run queue میگن..
  • حرف M: مخفف machine هست که به معنی ماشین مجازی یا همون نخ های سطح هسته هست.. تعدادشون رو میشه تغییر داد و مشخص کرد حداکثر تعدادشون چقدر باشه.. اما شاید فکر زیاد جالبی نباشه.. این کارو میشه با استفاده از پکیج debug و فانکشن debug.SetMaxThreads(n int) انجام داد.. به صورت دیفالت این مقدار 10000 هست

نکته در مورد GOMAXPROCS: تغییر این مقدار در زمان اجرا باعث میشه که روند اجرای برنامه به صورت همروند متوقف بشه.. یه اصطلاحی روش گذاشتن: دنیا واسه یه لحظه stop/start میشه.. (شروع روند gc هم برای یک لحظه همین کارو میکنه)

// GOMAXPROCS sets the maximum number of CPUs that can be executing // simultaneously and returns the previous setting. It defaults to // the value of runtime.NumCPU. If n < 1, it does not change the current setting. // This call will go away when the scheduler improves. func GOMAXPROCS(n int) int { if GOARCH == &quotwasm&quot && n > 1 { n = 1 // WebAssembly has no threads yet, so only one CPU is possible. } lock(&sched.lock) ret := int(gomaxprocs) unlock(&sched.lock) if n <= 0 || n == ret { return ret } stopTheWorldGC(&quotGOMAXPROCS&quot) // newprocs will be processed by startTheWorld newprocs = int32(n) startTheWorldGC() return ret }

تصویر زیر نمای دیداری کلی از زمان بند رانتایم هست.. اگه دقت کنید متوجه میشید ک شامل دو نوع صف global run queue و local run queue میشه..


در این صف ها که از نوع fifo هستن شامل گوروتین هایی میشن که در حالت runnable هستن.. قبل از اینکه ادامه بدیم.. حالت های گوروتین ها از نظر اجرا:

  • در حال اجرا: گوروتین به یکی از M ها attach شده و در حال اجراست
  • قابل اجرا یا runnable: گوروتین از حالت بلاک بیرون اومده و میتونه اجرا شه .. در یکی از صف های گلوبال یا لوکال قرار داره..
  • مسدود یا block: گوروتین به دلیلی بلاک شده..

اما بلاک شدن یک گوروتین میتونه دلایل مختلفی داشته باشه.. لیست زیر یک سری از دلایلی هست که ممکنه گوروتین بخاطرش بلاک و روند اجراش متوقف شده باشه

  • چنل ها.. ارسال یا دریافت روی یه چنل یا select و ..
  • mutex ها WaitGroup و غیره..
  • Network و IO و تایمر ها
  • Syscall هایی که زده میشه.. مثله باز کردن فایل
  • روند اجرای GC یا همون اشغال جم کن..
  • تغییر مقدار gomaxprocs در هنگام اجرا

زمان بند توی تایم اسلایس هایی در حدود هر 10 میلی ثانیه برای جلوگیری از به وجود اومدن گرسنگی گوروتین در حال اجرا رو به صف runnable منتقل میکنه.. سوال اصلی اینه که چرا باید اینکارو انجام بدیم؟ خب یه مثال ساده.. فرضا یه گوروتین دارید که به این صورت تعریف شده..

func main() { go infLoop() } func infLoop() { for {} }

توی مثال بالا گوروتین infLoop واسه هیچی بلاک نمیشه.. پس به همین دلیل همیشه به M یا همون thread سیستم عامل attach میمونه و باعث گرسنگی میشه.. به همین دلیل متوقف کردنش بعد از یک مدت ضروریه، از اونجایی که گوروتین ها context switch کم هزینه ای دارن این مدت زمان 10 میلی ثانیه مشکل ساز نمیشه ‌‌ ‌ ‌‌‌‌‌‌


‌ مثال بالا رو میتونید به صورت زیر زمان بند فرندلی کنید.. به زبون دیگه میتونید به زمان بند بگید که هوای بقیه گوروتین ها رو هم داشته باش.. البته همونطور که گفتم زمان بند بعد از 10 میلی ثانیه بیخیال این گوروتین میشه و از حالت در حال اجرا به runnable تبدیلش میکنه..

func main() { go infLoop() } func infLoop() { for { runtime.Gosched() } }

خب تقریبا یکم با زمان بند رانتایم اشنا شدیم.. بیاید یکم جزئیات رو برسی کنیم.. همونطور که گفته شد زمان بند شامل دو صف گلوبال و لوکال میشه که هر P صف لوکال خودشو داره، این صف از نوع ارایه ای به طول 256 هست، پس طول صف لوکال 256 المنت میشه.. این فیلد که با نام runq تعریف شده رو میتونید اینجا ببینید.. البته ساختار P فقط شامل این صف نیست. درواقع علاوه بر این صف یک فیلد دیگه هم تعریف شده به اسم runnext که فقط یک گوروتین توش قرار میگیره.. و گوروتینی که توی این فیلد قرار میگیره همیشه گوروتین بعدی برای اجرا (attach کردن به M) هست.. (این فیلد میتونه nil هم باشه) .. اما سوالی که پیش میاد اینه که چرا ما به این فیلد نیاز داریم؟ برای جواب دادن باید یه مثالیو مطرح کنیم..

var ch = make(chan string) func a() { go b() <-ch } func b() { ch <- doIO() }

توی این مثال گوروتین a گوروتین b رو میسازه و اجرا میکنه، بعد خودش بلاک میشه برای سیگنال روی چنل که باید از b بیاد.. حالا b هم خودش یکار io باندیو انجام میده .. بدون runnext قائدتا b باید بره تو ته صف گلوبال یا ته صف لوکال همین P .. خب اگه این اتفاق بیوفته شاهد افت پرفورمنس هستیم .. چرا؟ چون a که بلاک هست و b ته صف .. a فقط تو بلاکی مونده به خاطر اینکه b نرسیده اول صف هنوز، پس این b بهتره که اول از همه اجرا شه که حداقل بیخود a توی حالت بلاک نباشه.. البته اینجا شاید بگین خب این باعث گرسنگی میشه.. چون اگه دوتا گوروتین همدیگه رو به اصطلاح spawn کنن پس همیشه توی این فیلد میمونن و بقیه گوروتین ها اجرا نمیشن.. خب قانون 10 میلی ثانیه رو یادتونه؟ دقیقا اینجا هم صدق میکنه و b فقط برای 10 میلی ثانیه اجرا میشه و بعدش دیگه میره تو صف گلوبال یا لوکال و فیلد runnext مقدارش nil میشه.. وقتی این فیلد nil باشه پروسسور یا همون P گوروتینی رو از صف گلوبال یا لوکال اجرا میکنه.. پس اینجا هم با اینکه preemption صورت میگیره باز هم گرسنگی پیش نمیاد..

از طرفی صف گلوبال چون صفی هست که همه پروسسور ها بهش سر میزنن پس باید واسه دسترسی بهش یک قفلی هم اوکی کنیم.. منظور از قفل mutex هست، احتمالا با mutex اشنا باشید و ازش استفاده کرده باشید، دلیل وجود این قفل اینه که چون صف گلوبال منبع عمومی هست ممکنه دو پروسسور یا P به صورت هم زمان یک گوروتین رو روی دو thread یا همون M اجرا کن پس با این mutex مطمعن میشیم که این مشکل به وجود نمیاد..

اما گوروتین ها بر چه اساسی توی این صف ها قرار میگیرن؟ مثلا من اگه یه گروروتین اوکی کنم تو کدوم صف میره؟

  • اول وارد runnext میشه و به مدت یک تایم اسلایس (همون 10 میلی ثانیه) اجرا میشه
  • به local run queue همون P منتقل میشه
  • در صورتی که P کاملا پر شده بود، یعنی 256 تا گوروتین توی این صف داشتیم، نصفشون منتقل میشن به global run queue

و سوال بعدی اینه که P ها چطور گوروتین ها رو از توی این صف ها انتخاب و اجرا میکنند؟ واسه جواب به این سوال لازمه یه قسمت از سورس رانتایم رو برسی کنیم، کد زیر قسمتی از فانکشن ()schedule هست

if gp == nil { // Check the global runnable queue once in a while to ensure fairness. // Otherwise two goroutines can completely occupy the local runqueue // by constantly respawning each other. if _g_.m.p.ptr().schedtick%61 == 0 && sched.runqsize > 0 { lock(&sched.lock) gp = globrunqget(_g_.m.p.ptr(), 1) unlock(&sched.lock) } } if gp == nil { gp, inheritTime = runqget(_g_.m.p.ptr()) // We can see gp != nil here even if the M is spinning, // if checkTimers added a local goroutine via goready. } if gp == nil { gp, inheritTime = findrunnable() // blocks until work is available } // This thread is going to run a goroutine and is not spinning anymore, // so if it was marked as spinning we need to reset it now and potentially // start a new spinning M. if _g_.m.spinning { resetspinning() }

قبل از اینکه کد رو برسی کنیم باید بدونید که برای زمان بند یک کنتور یا counter تعریف شده به اسم schedtick که هر باری که زمان بندی انجام بشه این یکی اضافه میشه..

خب به شرط اول دقت کنید.. منظور از gp همون گوروتین هست.. شرط اول میگه اگه گوروتین نداشتی وارد بلاک شو .. وقتی وارد بلاک شرط اول بشیم میبینیم که یک شرط دیگه هست.. این شرط میگه اگه counter زمان بند باقی ماندش به 61 صفر و طول صف گلوبال بیشتر از 0 بود بیا گوروتینی که میخوای اجرا کنی رو از صف گلوبال بردار .. همونطوری که میبینید عمل lock و unlock کردن mutex هم انجام میشه.. توی شرط دوم باز میاد gp رو چک میکنه که مبادا nil باشه.. اگه اینطور بود پس میاد یه گوروتین از صف لوکال همون P برمیداره.. در نهایت توی شرط سوم اگه بازم چیزی واسه اجرا نداشت، استراتژی work stealing و spinning در پیش گرفته میشه.. اینا رو در ادامه برسی میکنیم قبلش بیاید به صورت کلی این شروطی که بررسی کردیم رو مرور کنیم:

  • بعد از هر 61 بار انتخاب گوروتین از صف لوکال یکبار هم از صف گلوبال انتخاب کن
  • اگه چیزی پیدا نشد برو واسه صف local run queue
  • اگه بازم چیزی نبود سعی کن از بقیه P ها گوروتین بدزدی (توضیح میدیم بیشتر)
  • اگه بقیه P ها هم آهی در بساط نداشتن دوباره صف global رو چک کن
  • اگه بازم چیزی نبود netpoller رو چک کن (توضیح میدیم درمودرش)

از مورد پنجم به بعد M این P یا همون نخ سیستم عاملی که زیر دست این P هست به حالت spinning thread در میاد.. این بحث رو بعد از بررسی استراتژی hands off بیشتر توضیح میدیم

توی مورد سوم گفتیم دزدی اتفاق میفته، اما P ها چطور گوروتین های همدیگه رو کش میرن؟ خیلی ساده به صورت رندوم یه P رو انتخاب و نصف گوروتین های صف لوکالش رو برای خودشون انتقال میدن

خب قبل از hand off و spinning و netpoller لازمه system call یا syscall ها رو بررسی کنیم.. system call ها چین؟ سیسکال ها درواقع درخواست انجام کار یا سرویسی هست که توسط user-space برای هسته سیستم عامل ارسال میشه.. خوندن فایل، بازکردن کانکشن، خاموش کردن سیستم و .. غیره همه با استفاده از syscall ها انجام میشن.. قبلا گفتیم که یکی از دلایلی که باعث بلاک شدن یک گوروتین میشه همین زدن سیستم کال هست.. اما یکم داستان پیچیده تر هست.. وقتی سیستم کال یک گوروتین توسط M یا نخ سیستم عامل اجرا بشه علاوه بر گوروتین، این M هم توی هسته بلاک میشه تا وقتی که syscall کامل بشه (مثلا فایل از رو دیسک سخت افزاری پیدا و باز بشه) خب از اونجایی که برنامه ما توی user-space ران میشه نمیتونیم بفهمیم الان این M یی که گیر انداختیم تو کرنل تا کی کارش تموم میشه، اوکی خب تا الان شاید با خودتون گفته باشید که پس تکلیف این P و گوروتین هایی که باید با این M اجرا بشن چی میشه؟ اینجاست که استراتژی hands off در پیش گرفته میشه

استراتژی hands off: قبل از اینکه M بخواد syscall یی رو اجرا کنه، باید M جایگزین دیگه ای پیدا کنه که جاشو پُر کنه.. تو این حالت P به اصطلاح assignment میشه به این M جدید و گوروتین ها میتونن اجرا شن.. الان M اصلی میتونه با خیال راحت syscall رو اجرا کنه.. بعد از کامل شدن syscall گوروتینی که براش بلاک شده بود به حالت runnable میره، اما تکلیف این M اصلی که syscall رو کامل کرده چی میشه؟ رانتایم به اصطلاح این M رو به حالت spinning یا park در میاره. از اونجایی که درخواست thread از سیستم عامل و از بین بردنش پرهزینه و زمان بره کار عاقلانه هم همین هست..

پس وقتی syscall زده میشه نخ یا M جدید از کجا پیداش میشه؟

  • از spinning thread ها استفاده میشه، قبلا گفتیم که اگه P توی صف ها گوروتینی واسه اجرا پیدا نکرده بود M یی که زیر دستش هست به این حالت در میاد، پس spinning thread میتونه توی چنین سناریو هایی به P ایده ال assign بشه.. اگه اخرین spinning thread یه کاری واسه انجام دادن پیدا کرد و خواست مشغول بشه، باید یک thread رو از سیستم عامل درخواست (unpark) کنه که اون thread شروع به چرخش کنه
  • بازم اگه چیزی پیدا نشد از سیستم عامل درخواست میشه

نخ های پارک شده چی هستن؟ نخ هایی هستن که توسط رانتایم از بین برده میشن

چه زمانی thread ها در حالت spinning هستن؟

  • نخ یا M یی که زیر دست P (یا assign شده به P) هست اما توی صف گلوبال، لوکال یا netpoller چیزی واسه اجرا پیدا نکرده باشه..
  • نخ یا M یی که زیر دست P (یا assign شده به P) نیست..
  • نخ یا M یی که توسط اخرین spinnig thread به اصطلاح unpark شده..

و netpoller چیه؟ به صورت خلاصه یه نوع چک کننده نوتیفیکیشن یا event checker هست.. سرور شما میتونه به دو حالت کانکشن رو accept کنه، blocking mode یا non-blocking mod .. توی حالت blocking نخ یا thread در انتظار اینکه file descriptor کانکشن مورد نظر اماده بشه بلاک میمونه.. تو این حالت برای هر کانکشن یک نخ لازمه (وب سرور اپاچی توی مود fork)

در حالت non-blocking که در لینوکس با epoll (سیسکال epoll_create) و در bsd ها با kqueue شناخته میشه، شما لازم نیست تا زمانی که file descriptor اماده بشه براش بلاک بمونید.. خود کرنل وقتی فلان file descriptor یا fd اماده خوندن شد بهتون خبر میده.. شما کافیه epoll_wait رو توی یک حلقه بینهایت ران کنید.. این epoll_wait وقتی event یی روی fd خاصی صورت بگیره خروجی میده بهتون .. و شما میتونید روی اون fd یا fd ها مثلا read انجام بدین .. همچنین epoll_ctl هم قابل استفاده هست ..

توی گولنگ ما یک netpoller داریم که درواقع همین حلقه for هست که توش epoll_wait تعریف شده.. وقتی netpoller نوتیفیکیشنی از هسته دریافت میکنه که میگه میتونی عملیات io روی فلان fd انجام بدی، به اطلاعاتی که از قبل داره روجوع میکنه که ببینه ایا گوروتین هایی برای این fd بلاک شدن یا نه .. و بهشون اطلاع میده .. لازم به ذکره که netpoller کلا thread خودشو داره..

به نظرتون میتونیم زمان بند رانتایم رو debug کنیم؟ یکم بفهمیم چی به چیه.. با ست کردن scheddetail=1,schedtrace=1000 برای env var یا همون متغیر GODEBUG میتونید اینکارو انجام بدید..
تستش کنیم ببینیم چطور میشه.. یه برنامه hello-world اوکی میکنیم

package main import &quotfmt&quot func main() { fmt.Println(&quothello world&quot) }

و در نهایت کامپایل و اجراش میکنیم..

# go build main.go # GODEBUG=scheddetail=1,schedtrace=1000 ./main SCHED 0ms: gomaxprocs=8 idleprocs=5 threads=5 spinningthreads=1 idlethreads=0 runqueue=0 gcwaiting=0 nmidlelocked=0 stopwait=0 sysmonwait=0 P0: status=1 schedtick=0 syscalltick=0 m=4 runqsize=1 gfreecnt=0 timerslen=0 P1: status=1 schedtick=0 syscalltick=0 m=2 runqsize=0 gfreecnt=0 timerslen=0 P2: status=0 schedtick=0 syscalltick=0 m=-1 runqsize=0 gfreecnt=0 timerslen=0 P3: status=0 schedtick=0 syscalltick=0 m=-1 runqsize=0 gfreecnt=0 timerslen=0 P4: status=0 schedtick=0 syscalltick=0 m=-1 runqsize=0 gfreecnt=0 timerslen=0 P5: status=0 schedtick=0 syscalltick=0 m=-1 runqsize=0 gfreecnt=0 timerslen=0 P6: status=0 schedtick=0 syscalltick=0 m=-1 runqsize=0 gfreecnt=0 timerslen=0 P7: status=0 schedtick=0 syscalltick=0 m=-1 runqsize=0 gfreecnt=0 timerslen=0 M4: p=0 curg=-1 mallocing=0 throwing=0 preemptoff= locks=1 dying=0 spinning=false blocked=false lockedg=-1 M2: p=1 curg=-1 mallocing=0 throwing=0 preemptoff= locks=3 dying=0 spinning=false blocked=false lockedg=-1 M1: p=-1 curg=-1 mallocing=0 throwing=0 preemptoff= locks=2 dying=0 spinning=false blocked=false lockedg=-1 M0: p=-1 curg=-1 mallocing=0 throwing=0 preemptoff= locks=1 dying=0 spinning=false blocked=false lockedg=1 G1: status=1(chan receive) m=-1 lockedm=0 G2: status=1() m=-1 lockedm=-1 G3: status=1() m=-1 lockedm=-1 G4: status=4(GC scavenge wait) m=-1 lockedm=-1 hello world

لاین اول یک سری اطلاعات مهمی بهمون میده که به صورت خلاصه بررسیشون میکنیم

  • مقدار SCHED 0ms: مدت زمان اجرا
  • مقدار gomaxprocs=8: تعداد gomaxprocs هست
  • مقدار idleprocs=5: پروسسور یا P هایی که سرشون شلوغ نیست
  • مقدار threads=5: تعداد نخ هایی که توسط رانتایم مدیریت میشن
  • مقدار spinningthreads=1: نخ هایی که درحالت چرخش هستن
  • مقدار idlethreads=0: نخ هایی که سرشون شلوغ نیست
  • مقدار runqueue=0: طول صف گلوبال هست
  • مقدار gcwaiting=0: عمل اشغال جم کن یا gc اگه شروع بشه اجرا گوروتین ها متوقف میشه و این مقدار میشه یک
  • مقدار nmidlelocked=0: تعداد M های lock شده که منتظر انجام کار هستن
  • مقدار stopwait=0:‌مشخص کننده حالت دنیا هست.. مثلا اگه عدد 0x7fffffff بشه زمان بند گوروتین جدیدی رو start نمیکنه
  • مقدار sysmonwait=0: مربوط به sysmon مانیتورینگ رانتایم میشه..

به status گوروتین ها یا G ها توجه کنید.. این اعداد که از صفر تا 9 هستن مشخص کننده حالت گوروتین هستن.

  • عدد 0 : Gidle هست، گوروتین الوکیت شده اما هنوز initialize نشده.
  • عدد 1 : Grunnable هست، گروتین قابل اجرا هست و توی صف گلوبال یا لوکال قرار داره
  • عدد 2 : Grunning هست، گوروتین درحال اجرا هست
  • عدد 3 : Gsyscall هست، گوروتین در حال انجام system call هست
  • عدد 4 : Gwaiting، گوروتین در انتظار رانتایم هست.
  • عدد 5 : Gmoribund_unused، در حال حاظر استفاده نمیشه و فقط توی سورس کد قرار داده شده
  • عدد 6 : Gdead، گوروتین exit شده یا تازه initialize شده باشه
  • عدد 7 : Genqueue، در حال حاظر استفاده نمیشه
  • عدد 8 : Gcopystack، استک گوروتین در حال انتقال هست و در صف ها قرار نمیگیره
  • عدد 9 : Gpreempted، گوروتین روند اجرای خودش رو برای یک گوروتین suspend شده متوقف میکنه.. اون گوروتین preempt میشه

خیلی خب میشه گفت به صورت کلی تقریبا با زمان بند رانتایم گولنگ اشنا شدیم، البته خیلی از جزئیات مطرح نشدن چون در حدود این پُست نیستن و باید بیشتر به صورت تخصصی بهشون پرداخته بشه.. در نهایت امیدوارم از این پست لذت برده باشید.. کامنت و نظر رو فراموش نکنید.

این پُست از وبلاگ توسعه دهندگان blog.snix.ir کپی برداری شده است

گولنگgogolangRunTimescheduler
توسعه دهنده و مهندس شبکه
شاید از این پست‌ها خوشتان بیاید