Go垃圾回收之并行標(biāo)記執(zhí)行模式
標(biāo)記準(zhǔn)備階段切換到后臺(tái)標(biāo)記協(xié)程
標(biāo)記準(zhǔn)備階段的第二個(gè)問(wèn)題是如何切換到后臺(tái)標(biāo)記協(xié)程執(zhí)行。在標(biāo)記準(zhǔn)備階段執(zhí)行了STW、在STW階短暫的暫停了所有的協(xié)程??梢灶A(yù)料到,當(dāng)關(guān)閉STW準(zhǔn)備再次啟動(dòng)所有的協(xié)程時(shí),每一個(gè)邏輯處理器P會(huì)進(jìn)入一輪新的調(diào)度循環(huán),在調(diào)度循環(huán)的最開(kāi)始的一步會(huì)判斷是否處于GC階段,如果是,嘗試判斷當(dāng)前P 是否需要執(zhí)行后臺(tái)標(biāo)記任務(wù)。
func schedule() {
// 正在 GC,去找 GC 的 g
if gp == nil && gcBlackenEnabled != 0 {
gp = gcController.findRunnableGCWorker(_g_.m.p.ptr())
tryWakeP = tryWakeP || gp != nil
}
}如果代表了執(zhí)行完整的后臺(tái)標(biāo)記協(xié)程的字段dedicatedMarkWorkersNeeded大于0,則直接執(zhí)行后臺(tái)標(biāo)記任務(wù)。否則,如果協(xié)助協(xié)程字段fractionalUtilizationGoal大于0,并且當(dāng)前P執(zhí)行標(biāo)記任務(wù)的時(shí)間 小于 fractionalUtilizationGoal*當(dāng)前標(biāo)記周期總時(shí)間,仍然會(huì)執(zhí)行后臺(tái)標(biāo)記任務(wù),但是并不會(huì)在整個(gè)標(biāo)記周期內(nèi)一直執(zhí)行。本小節(jié)下面會(huì)看到,這對(duì)應(yīng)著后臺(tái)標(biāo)記協(xié)程的不同執(zhí)行模式。
if decIfPositive(&c.dedicatedMarkWorkersNeeded) {
_p_.gcMarkWorkerMode = gcMarkWorkerDedicatedMode
} else if c.fractionalUtilizationGoal == 0 {
return nil
} else {
delta := nanotime() - gcController.markStartTime
if delta > 0 && float64(_p_.gcFractionalMarkTime)/float64(delta) > c.fractionalUtilizationGoal {
return nil
}
_p_.gcMarkWorkerMode = gcMarkWorkerFractionalMode
}并行標(biāo)記執(zhí)行階段
在并發(fā)標(biāo)記執(zhí)行階段,后臺(tái)標(biāo)記協(xié)程可以與執(zhí)行用戶代碼的協(xié)程并行執(zhí)行。Go語(yǔ)言的目標(biāo)是后臺(tái)標(biāo)記協(xié)程暫用CPU的時(shí)間為25%,最大限度不因?yàn)閳?zhí)行GC而中斷或減慢用戶協(xié)程的執(zhí)行。后臺(tái)標(biāo)記協(xié)程有3種不同的模式:

DedicatedMode代表處理器專(zhuān)門(mén)負(fù)責(zé)標(biāo)記對(duì)象,不會(huì)被調(diào)度器搶占;
FractionalMode代表協(xié)助后臺(tái)標(biāo)記,其在整個(gè)標(biāo)記階段只會(huì)花費(fèi)一定部分時(shí)間執(zhí)行,因此,在標(biāo)記階段當(dāng)完成時(shí)間的目標(biāo)后,會(huì)自動(dòng)退出。
IdleMode 為當(dāng)處理器沒(méi)有查找到可以執(zhí)行的 協(xié)程時(shí),執(zhí)行垃圾收集的標(biāo)記任務(wù)直到被搶占。標(biāo)記階段的核心邏輯位于gcDrain 函數(shù),第二個(gè)參數(shù)為flag位,大部分flag和后臺(tái)標(biāo)記協(xié)程的3種不同的模式有關(guān)。
func gcDrain(gcw *gcWork, flags gcDrainFlags)flag有4種,用于指定后臺(tái)標(biāo)記協(xié)程的不同行為。gcDrainUntilPreempt 為當(dāng) Goroutine 的 preempt 字段被設(shè)置成 true 時(shí)返回, 代表當(dāng)前后臺(tái)標(biāo)記可以被搶占。gcDrainFlushBgCredit計(jì)算后臺(tái)完成的標(biāo)記任務(wù)量以減少并行標(biāo)記期間用戶程序執(zhí)行輔助垃圾收集的工作量,后面還會(huì)詳細(xì)介紹。gcDrainIdle對(duì)應(yīng)IdleMode模式, 當(dāng)處理器上包含其他待執(zhí)行 協(xié)程 時(shí)退出.gcDrainFractional 對(duì)應(yīng)IdleMode模式,當(dāng)完成目標(biāo)時(shí)間后退出。

由于在DedicatedMode模式下,將會(huì)一直執(zhí)行后臺(tái)標(biāo)記任務(wù),這意味著當(dāng)前P本地隊(duì)列中的協(xié)程將一直得不到執(zhí)行,這是不能接受的。所以Go語(yǔ)言中的做法是首先執(zhí)行可以被搶占的后臺(tái)標(biāo)記任務(wù),如果發(fā)現(xiàn)被其他協(xié)程搶占了,當(dāng)前的P并不會(huì)執(zhí)行其他協(xié)程。而是會(huì)選擇將其他協(xié)程轉(zhuǎn)移到全局隊(duì)列中,并取消gcDrainUntilPreempt標(biāo)志,開(kāi)始執(zhí)行不能夠被搶占的模式。
case gcMarkWorkerDedicatedMode:
gcDrain(&_p_.gcw, gcDrainUntilPreempt|gcDrainFlushBgCredit)
if gp.preempt {
lock(&sched.lock)
for {
gp, _ := runqget(_p_)
if gp == nil {
break
}
globrunqput(gp)
}
unlock(&sched.lock)
}
gcDrain(&_p_.gcw, gcDrainFlushBgCredit)
case gcMarkWorkerFractionalMode:
gcDrain(&_p_.gcw, gcDrainFractional|gcDrainUntilPreempt|gcDrainFlushBgCredit)
case gcMarkWorkerIdleMode:
gcDrain(&_p_.gcw, gcDrainIdle|gcDrainUntilPreempt|gcDrainFlushBgCredit)
}對(duì)于FractionalMode模式和IdleMode模式,都允許被搶占。除此之外,F(xiàn)ractionalMode模式加上了gcDrainFractional標(biāo)志表明當(dāng)前協(xié)程會(huì)在到達(dá)目標(biāo)時(shí)間后退出,IdleMode模式加上了gcDrainIdle標(biāo)志表明會(huì)在發(fā)現(xiàn)有其他協(xié)程可以運(yùn)行時(shí)退出。最后三種模式都加上了gcDrainFlushBgCredit標(biāo)志,用于計(jì)算后臺(tái)完成的標(biāo)記任務(wù)量,并喚醒之前由于分配內(nèi)存太頻繁而陷入等待的用戶協(xié)程(關(guān)于輔助標(biāo)記,將在后面介紹)。
總結(jié)
并發(fā)標(biāo)記階段有多種模式,這些模式的目的是為了后臺(tái)標(biāo)記協(xié)程占用25%的CPU時(shí)間,協(xié)調(diào)好后臺(tái)標(biāo)記協(xié)程與用戶協(xié)程的關(guān)系,這只是比較宏觀的角度來(lái)討論了并發(fā)標(biāo)記階段。并發(fā)標(biāo)記階段的故事后面還更加精彩.....
推薦閱讀
