上卷 程序设计
中卷 标准库
- bufio 1.18
- bytes 1.18
- io 1.18
- container 1.18
- encoding 1.18
- crypto 1.18
- hash 1.18
- index 1.18
- sort 1.18
- context 1.18
- database 1.18
- connection
- query
- queryrow
- exec
- prepare
- transaction
- scan & null
- context
- tcp
- udp
- http
- server
- handler
- client
- h2、tls
- url
- rpc
- exec
- signal
- embed 1.18
- plugin 1.18
- reflect 1.18
- runtime 1.18
- KeepAlived
- ReadMemStats
- SetFinalizer
- Stack
- sync 1.18
- atomic
- mutex
- rwmutex
- waitgroup
- cond
- once
- map
- pool
- copycheck
- nocopy
- unsafe 1.18
- fmt 1.18
- log 1.18
- math 1.18
- time 1.18
- timer
下卷 运行时
源码剖析
附录
3.4.1 流程
设置好标记,一旦解除 STW,调度核心(schedule)会立即检测到垃圾回收被启动。
随后,查找 P 绑定的工人(worker goroutine),执行标记工作。
1. start : gcBgMarkStartWorkers, stop!, startCycle, Blacken = 1, start!。
2. schedule : findrunnable --> G gcBgMarkWorker --> execute。
3. worker : gcBgMarkWorker --> gcDrain --> gcMarkDone (stop!, Blacken = 0)
--> schedEnableUser
--> endCycle
--> gcMarkTermination (start!)。
// proc.go func schedule() { if gp == nil && gcBlackenEnabled != 0 { gp = gcController.findRunnableGCWorker(_g_.m.p.ptr()) } if gp == nil { gp, inheritTime = findrunnable() // blocks until work is available } execute(gp, inheritTime) }
虽然每个 P 都绑定有工人,但却承担不同的角色。
依据参与和抢占方式,分为不同类别:
- gcMarkWorkerDedicatedMode : 正式工。专注标记工作,无法抢占。
- gcMarkWorkerFractionalMode : 小时工。按需参加一定时长的工作,可被抢占。
- gcMarkWorkerIdleMode : 临时工。没其他任务,临时参加标记工作,可被抢占。
各工种数量由 startCycle 方法,按利用率动态计算。
回收 CPU 利用率预期是 25%,以正式工为主,辅以临时工和小时工。
当利用率达不到预期时,会启动小时工参与。
临时工仅在找不到可执行任务时参与。
// mgc.go const gcBackgroundUtilization = 0.25 func (c *gcControllerState) startCycle() { totalUtilizationGoal := float64(gomaxprocs) * gcBackgroundUtilization c.dedicatedMarkWorkersNeeded = int64(totalUtilizationGoal + 0.5) ... }
findRunnableGCWorker, findrunnable
调度器按需激活不同类别的工人,返回(find)并使其进入(execute)工作状态。
// mgc.go func (c *gcControllerState) findRunnableGCWorker(_p_ *p) *g { // 检查是否可以启用。 if !gcMarkWorkAvailable(_p_) { return nil } // 按需设置工作模式。 if decIfPositive(&c.dedicatedMarkWorkersNeeded) { _p_.gcMarkWorkerMode = gcMarkWorkerDedicatedMode } else if c.fractionalUtilizationGoal == 0 { return nil } else { ... _p_.gcMarkWorkerMode = gcMarkWorkerFractionalMode } gp := _p_.gcBgMarkWorker.ptr() casgstatus(gp, _Gwaiting, _Grunnable) return gp }
检查条件包括本地队列、全局队列,以及根对象扫描计数确定是否可以启用。
// mgc.go func gcMarkWorkAvailable(p *p) bool { if p != nil && !p.gcw.empty() { return true } if !work.full.empty() { return true // global work available } if work.markrootNext < work.markrootJobs { return true // root scan work available } return false }
另一函数 findrunnable,让找不到任务的 P 以临时工身份参与标记。
// proc.go func findrunnable() (gp *g, inheritTime bool) { ... if gcBlackenEnabled != 0 && _p_.gcBgMarkWorker != 0 && gcMarkWorkAvailable(_p_) { _p_.gcMarkWorkerMode = gcMarkWorkerIdleMode gp := _p_.gcBgMarkWorker.ptr() casgstatus(gp, _Gwaiting, _Grunnable) return gp, false } }
gcBgMarkWorker
工人的工作过程,贯穿 gcBgMarkWorker、gcDrain、gcMarkDone 函数。
工人从根对象(root)开始标记,将灰色对象添加到 P 本地或全局队列中。然后处理本地和全局队列。工人可能因抢占调度、其他任务,或者找不到任务(本地和全局队列为空,不包括其他 P 本地队列)等原因退出。
退出前,补上自己所消耗名额(work.nwait)。如此,在结束前,调度器会让其他该类型工人,继续完成工作。直到最后一位工人因找不到任务而退出时,才整体转入结束阶段。
// mgc.go func gcBgMarkWorker(_p_ *p) { ... for { // 休眠,等待唤醒执行。 gopark(...) // 占用名额,递减等待人数。 decnwait := atomic.Xadd(&work.nwait, -1) // 开始标记工作。 systemstack(func() { switch _p_.gcMarkWorkerMode { case gcMarkWorkerDedicatedMode: gcDrain(&_p_.gcw, gcDrainFlushBgCredit) case gcMarkWorkerFractionalMode: gcDrain(&_p_.gcw, gcDrainFractional|gcDrainUntilPreempt|gcDrainFlushBgCredit) case gcMarkWorkerIdleMode: gcDrain(&_p_.gcw, gcDrainIdle|gcDrainUntilPreempt|gcDrainFlushBgCredit) } }) // 退出前(比如完成任务或被抢占)补上占用的名额。 incnwait := atomic.Xadd(&work.nwait, +1) // 如果等待人数齐员,且全局队列没有可用任务,表示标记任务结束。 if incnwait == work.nproc && !gcMarkWorkAvailable(nil) { gcMarkDone() } } }
gcDrainUntilPreempt: 抢占时退出。
gcDrainIdle: 有其他任务时退出。
gcDrainFractional: 利用率超过阈值(20%)时退出。
gcDrainFlushBgCredit: 保存信用值。
名额设置很有意思。假定有很多工人,反正只要两个能增减平衡即可。
// mgc.go 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) }
gcMarkDone
由于工人会中途退出(exit gcDrain),有必要检查所有 P 本地队列是否有遗留任务。
遗留任务必须上交到全局队列,以便调度器启动新工人完成收尾任务。
// mgc.go func gcMarkDone() { top: // 重新检查,如果任务未完成,则停止后续操作。 if !(gcphase == _GCmark && work.nwait == work.nproc && !gcMarkWorkAvailable(nil)) { return } // 确保所有 P 上交任务。 gcMarkDoneFlushed = 0 systemstack(func() { forEachP(func(_p_ *p) { // 上交任务。 _p_.gcw.dispose() if _p_.gcw.flushedWork { atomic.Xadd(&gcMarkDoneFlushed, 1) _p_.gcw.flushedWork = false } }) casgstatus(gp, _Gwaiting, _Grunning) }) // 有遗留任务上交,重新检查,避免提前终止。 if gcMarkDoneFlushed != 0 { goto top } // 转入标记结束阶段 。。。。。。 // STW! systemstack(stopTheWorldWithSema) // 禁用工人和辅助。 atomic.Store(&gcBlackenEnabled, 0) // 唤醒被休眠的辅助。 gcWakeAllAssists() // 重新开始用户逻辑。 schedEnableUser(true) // 结束控制器周期,计算下次触发比率。 nextTriggerRatio := gcController.endCycle() // 标记终止!解除 STW! gcMarkTermination(nextTriggerRatio) }
gcMarkTermination
标记结束时,设置相关状态,通知调度器停止相关工作。
随后完成一些剩余工作,开启并发清理。
调度器会检查到该状态(gcBlackenEnabled = 0),从而不再参与回收工作。
// mgc.go func gcMarkTermination(nextTriggerRatio float64) { // 重置相关状态。 atomic.Store(&gcBlackenEnabled, 0) setGCPhase(_GCmarktermination) // 收尾检查和统计。 systemstack(func() { gcMark(startTime) }) // 开启清理。(禁用写屏障) systemstack(func() { setGCPhase(_GCoff) gcSweep(work.mode) }) // 更新回收阈值等。 gcSetTriggerRatio(nextTriggerRatio) // 解除 STW ! systemstack(func() { startTheWorldWithSema(true) }) // 释放未使用的 stack 块。 systemstack(freeStackSpans) // 非并发清理模式,当前 goroutine 将暂停。 if !concurrentSweep { Gosched() } }
func gcMark(start_time int64) { work.tstart = start_time // 确保没有任务遗漏,否则抛出异常 ... cachestats() memstats.heap_marked = work.bytesMarked memstats.heap_live = work.bytesMarked }
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论