返回介绍

上卷 程序设计

中卷 标准库

下卷 运行时

源码剖析

附录

3.4.1 流程

发布于 2024-10-12 19:15:57 字数 10897 浏览 0 评论 0 收藏 0

首先,调用 startCycle 开始新周期。按 25% CPU 利用率计算正式工和小时工数值。

// mgc.go

const gcBackgroundUtilization = 0.25
// mgc.go

// startCycle resets the GC controller's state and computes estimates
// for a new GC cycle. 

func (c *gcControllerState) startCycle(markStartTime int64, procs int) {
    
    // 确保目标比 heapLive 大一点。
    if goexperiment.PacerRedesign {
        if c.heapGoal < c.heapLive+64<<10 {
            c.heapGoal = c.heapLive + 64<<10
        }
    } else {
        if c.heapGoal < c.heapLive+1<<20 {
            c.heapGoal = c.heapLive + 1<<20
        }
    }

    // CPU 利用率(25%)。
    totalUtilizationGoal := float64(procs) * gcBackgroundUtilization    

    // 职业工(dedicated)取整,小数为小时工(fractional)。
    c.dedicatedMarkWorkersNeeded = int64(totalUtilizationGoal + 0.5)
    utilError := float64(c.dedicatedMarkWorkersNeeded)/totalUtilizationGoal - 1
    const maxUtilError = 0.3
    if utilError < -maxUtilError || utilError > maxUtilError {
        if float64(c.dedicatedMarkWorkersNeeded) > totalUtilizationGoal {
            // Too many dedicated workers.
            c.dedicatedMarkWorkersNeeded--
        }
        c.fractionalUtilizationGoal = (totalUtilizationGoal - float64(c.dedicatedMarkWorkersNeeded)) / float64(procs)
    } else {
        c.fractionalUtilizationGoal = 0
    }
    
    // 当 GODEBUG=gcstoptheword 关闭并发,全部成为正式工。
    if debug.gcstoptheworld > 0 {
        c.dedicatedMarkWorkersNeeded = int64(procs)
        c.fractionalUtilizationGoal = 0
    }
    
    // 清除所有 P 相关状态。
    for _, p := range allp {
        p.gcAssistTime = 0
        p.gcFractionalMarkTime = 0
    }
    
    // 计算辅助回收比率。
    c.revise()
}

随后,解除 STW,让 MP 重回调度循环(schedule)。在这里,检查垃圾回收状态,按需激活 worker.G 开始垃圾标记工作。

// proc.go

func schedule() {
	if gp == nil && gcBlackenEnabled != 0 {
		gp = gcController.findRunnableGCWorker(_g_.m.p.ptr())
	}    
    
    execute(gp, inheritTime)
}
// mgcpacer.go

// findRunnableGCWorker returns a background mark worker for _p_ if it
// should be run. This must only be called when gcBlackenEnabled != 0.

func (c *gcControllerState) findRunnableGCWorker(_p_ *p) *g {

    // 检查任务队列。
    // 刚启动时,gcMarkRootPrepare 准备了 work.markrootJobs 任务。
	if !gcMarkWorkAvailable(_p_) {
		return nil
	}

    // 取一个预先创建的 worker G。
	node := (*gcBgMarkWorkerNode)(gcBgMarkWorkerPool.pop())
	if node == nil {
		return nil
	}

    // 减法(lock-free)。
	decIfPositive := func(ptr *int64) bool {
		for {
			v := atomic.Loadint64(ptr)
			if v <= 0 {
				return false
			}

			if atomic.Casint64(ptr, v, v-1) {
				return true
			}
		}
	}

    // 消费工人名额,设置 P.worker 工种。
	if decIfPositive(&c.dedicatedMarkWorkersNeeded) {
        
		// This P is now dedicated to marking until the end of
		// the concurrent mark phase.
		_p_.gcMarkWorkerMode = gcMarkWorkerDedicatedMode
        
	} else if c.fractionalUtilizationGoal == 0 {
        
		// No need for fractional workers.
		gcBgMarkWorkerPool.push(&node.node)
        
		return nil
	} else {
        
		// Is this P behind on the fractional utilization goal?
		delta := nanotime() - c.markStartTime
		if delta > 0 && float64(_p_.gcFractionalMarkTime)/float64(delta) > c.fractionalUtilizationGoal {
			// Nope. No need to run a fractional worker.
			gcBgMarkWorkerPool.push(&node.node)
			return nil
		}
        
		// Run a fractional worker.
		_p_.gcMarkWorkerMode = gcMarkWorkerFractionalMode
	}

	// 修改 worker.G 状态,并返回。
	gp := node.gp.ptr()
	casgstatus(gp, _Gwaiting, _Grunnable)
    
	return gp
}

其他 MP 调度不到任务时,也尝试以临时工(idle)身份参与回收工作。

// proc.go

func findrunnable() (gp *g, inheritTime bool) {
    
	// We have nothing to do.
	//
	// If we're in the GC mark phase, can safely scan and blacken objects,
	// and have work to do, run idle-time marking rather than give up the
	// P.
    
	if gcBlackenEnabled != 0 && gcMarkWorkAvailable(_p_) {
		node := (*gcBgMarkWorkerNode)(gcBgMarkWorkerPool.pop())
		if node != nil {
			_p_.gcMarkWorkerMode = gcMarkWorkerIdleMode
			gp := node.gp.ptr()
			casgstatus(gp, _Gwaiting, _Grunnable)
            
			return gp, false
		}
	}    
}

工作

工人醒来后,开始标记存活对象。

// mgc.go

func gcBgMarkWorker() {
    
    gp := getg()
    node := new(gcBgMarkWorkerNode)
    node.gp.set(gp)
    
    ...
    
    // 在一个周期内,非专业工种会被抢占退出。而后可能再次
    // 进入标记工作。所以,该循环是必要的。
    for {
        
        // 休眠,直到被 findRunnableGCWorker 唤醒。
        gopark(func(g *g, nodep unsafe.Pointer) bool {
            
            // 将当前 worker G 放回池内。
            node := (*gcBgMarkWorkerNode)(nodep)
            gcBgMarkWorkerPool.push(&node.node)
            
            return true
        }, unsafe.Pointer(node), waitReasonGCWorkerIdle, traceEvGoBlock, 0)
        
        
        // 唤醒后准备工作。
        pp := gp.m.p.ptr()        
        startTime := nanotime()
        pp.gcMarkWorkerStartTime = startTime

        // 等待人数 -1 。
        decnwait := atomic.Xadd(&work.nwait, -1)
        
        // 工作!
        systemstack(func() {
            casgstatus(gp, _Grunning, _Gwaiting)
            
            switch pp.gcMarkWorkerMode {
            default:
                throw("gcBgMarkWorker: unexpected gcMarkWorkerMode")
                
            case gcMarkWorkerDedicatedMode:
                gcDrain(&pp.gcw, gcDrainUntilPreempt|gcDrainFlushBgCredit)
                
                if gp.preempt {
                    // We were preempted. This is
                    // a useful signal to kick
                    // everything out of the run
                    // queue so it can run
                    // somewhere else.
                    if drainQ, n := runqdrain(pp); n > 0 {
                        lock(&sched.lock)
                        globrunqputbatch(&drainQ, int32(n))
                        unlock(&sched.lock)
                    }
                }
                
                // Go back to draining, this time
                // without preemption.
                gcDrain(&pp.gcw, gcDrainFlushBgCredit)
                
            case gcMarkWorkerFractionalMode:
                gcDrain(&pp.gcw, gcDrainFractional|gcDrainUntilPreempt|gcDrainFlushBgCredit)
                
            case gcMarkWorkerIdleMode:
                gcDrain(&pp.gcw, gcDrainIdle|gcDrainUntilPreempt|gcDrainFlushBgCredit)
            }
            
            casgstatus(gp, _Gwaiting, _Grunning)
        })
        
        // 工作结束,统计时间。
        duration := nanotime() - startTime
        gcController.logWorkTime(pp.gcMarkWorkerMode, duration)
        if pp.gcMarkWorkerMode == gcMarkWorkerFractionalMode {
            atomic.Xaddint64(&pp.gcFractionalMarkTime, duration)
        }
        
        // 等待人数 +1,去掉工种。
        incnwait := atomic.Xadd(&work.nwait, +1)
        pp.gcMarkWorkerMode = gcMarkWorkerNotWorker
        
        // 如果所有工人都进入等待状态,且没有剩余工作。标记结束!
        if incnwait == work.nproc && !gcMarkWorkAvailable(nil) {
            node.m.set(nil)
            gcMarkDone()
        }
    }
}

等待总人数是多少,其实不重要。只要 work.nproc == work.nwait,就表示没有人在工作。

// mgc.go

// gcBgMarkPrepare sets up state for background marking.
func gcBgMarkPrepare() {
    
	// Background marking will stop when the work queues are empty
	// and there are no more workers (note that, since this is
	// concurrent, this may be a transient state, but mark
	// termination will clean it up). Between background workers
	// and assists, we don't really know how many workers there
	// will be, so we pretend to have an arbitrarily large number
	// of workers, almost all of which are "waiting". While a
	// worker is working it decrements nwait. If nproc == nwait,
	// there are no workers.
    
	work.nproc = ^uint32(0)
	work.nwait = ^uint32(0)
}

结束本次回收周期前,需要处理一些额外状况。

// mgc.go

// gcMarkDone transitions the GC from mark to mark termination if all
// reachable objects have been marked (that is, there are no grey
// objects and can be no more in the future). Otherwise, it flushes
// all local work to the global queues where it can be discovered by
// other workers.

func gcMarkDone() {
    
	// Ensure only one thread is running the ragged barrier at a time.
	semacquire(&work.markDoneSema)

top:

    // 再次检查。如任务未完成,则不能终止。
	if !(gcphase == _GCmark && work.nwait == work.nproc && !gcMarkWorkAvailable(nil)) {
		semrelease(&work.markDoneSema)
		return
	}

	semacquire(&worldsema)
	gcMarkDoneFlushed = 0
    
	systemstack(func() {
		gp := getg().m.curg
		casgstatus(gp, _Grunning, _Gwaiting)
        
        // 确保所有 P 处理好本地任务。        
		forEachP(func(_p_ *p) {
			wbBufFlush1(_p_)
			_p_.gcw.dispose()
            
			if _p_.gcw.flushedWork {
				atomic.Xadd(&gcMarkDoneFlushed, 1)
				_p_.gcw.flushedWork = false
			}
		})
        
		casgstatus(gp, _Gwaiting, _Grunning)
	})

	if gcMarkDoneFlushed != 0 {
		// More grey objects were discovered since the
		// previous termination check, so there may be more
		// work to do. 
		semrelease(&worldsema)
		goto top
	}
    
    // 记录结束时间,并再次 STW !!!
	now := nanotime()
	work.tMarkTerm = now
	work.pauseStart = now
	systemstack(stopTheWorldWithSema)
    
    // 禁用工人和辅助。
	atomic.Store(&gcBlackenEnabled, 0)
    
    // 唤醒被休眠的辅助。
	gcWakeAllAssists()

	semrelease(&work.markDoneSema)
	schedEnableUser(true)

    // 本周期结束。
	nextTriggerRatio := gcController.endCycle(now, int(gomaxprocs), work.userForced)
	gcMarkTermination(nextTriggerRatio)
}

终止

启动清理任务,通知调度器停止相关工作。

// mgc.go

// World must be stopped and mark assists and background workers must be
// disabled.

func gcMarkTermination(nextTriggerRatio float64) {
    
	setGCPhase(_GCmarktermination)

    // 收尾。
	systemstack(func() {
		gcMark(startTime)
	})

	// 标记结束(禁用写屏障),开启清理。
	systemstack(func() {
		setGCPhase(_GCoff)
		gcSweep(work.mode)
	})

	// Update GC trigger and pacing for the next cycle ...
	// Update timing memstats ...
	// Update work.totaltime ...
	// Compute overall GC CPU utilization ...

    // 唤醒等待标记结束的 Gs。(runtime.GC)
	lock(&work.sweepWaiters.lock)
	injectglist(&work.sweepWaiters.list)
	unlock(&work.sweepWaiters.lock)

	sl := sweep.active.begin()

    // 解除 STW !!!
	systemstack(func() { startTheWorldWithSema(true) })

    // 回收 workbuf、stackpool、mcache.stackcache、mcache.alloc ...
    
	sweep.active.end(sl)

	if !concurrentSweep {
		Gosched()
	}
}

因 gcMarkDone gcBlackenEnabled = 0 ,调度自然不会再去折腾垃圾回收。

// proc.go

func schedule() {
	if gp == nil && gcBlackenEnabled != 0 {
		gp = gcController.findRunnableGCWorker(_g_.m.p.ptr())
	}    
}

func findrunnable() (gp *g, inheritTime bool) {
	if gcBlackenEnabled != 0 && gcMarkWorkAvailable(_p_) {
		node := (*gcBgMarkWorkerNode)(gcBgMarkWorkerPool.pop())
	}    
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文