返回介绍

上卷 程序设计

中卷 标准库

下卷 运行时

源码剖析

附录

2.5.2 异步释放

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

后台异步释放由独立 goroutine 执行。

// proc.go

// The main goroutine.
func main() {
    gcenable()
}
// mgc.go

// gcenable is called after the bulk of the runtime initialization,
// just before we're about to start letting user code run.
// It kicks off the background sweeper goroutine, the background
// scavenger goroutine, and enables GC.

func gcenable() {
	go bgscavenge(c)
    <-c
}

该 G 以循环方式执行,单次释放足量内存。如释放未果,表示当前没有 “多余” 物理内存,阻塞后等待手工唤醒,否则以定时器唤醒。

// mgcscavenge.go

// Background scavenger.
//
// The background scavenger maintains the RSS of the application below
// the line described by the proportional scavenging statistics in
// the mheap struct.

func bgscavenge(c chan int) {
    
	scavenge.g = getg()
	scavenge.parked = true

    // 定时器,唤醒下面循环里的 sleep 操作。
	scavenge.timer = new(timer)
	scavenge.timer.f = func(_ any, _ uintptr) {
		wakeScavenger()
	}

    // 解除 gcenable 阻塞。
	c <- 1
    
    // 初始阻塞。(程序刚启动,没什么要释放的。等待 sysmon、timer 之类的唤醒)
	goparkunlock(&scavenge.lock, waitReasonGCScavengeWait, traceEvGoBlock, 1)

	// 休眠频率计算 ...
    
	for {
		released := uintptr(0)
		crit := float64(0)

        // 单次操作至少运行 1 毫秒。
		const minCritTime = 1e6
		for crit < minCritTime {
            
			// 条件阈值。
			retained, goal := heapRetained(), atomic.Load64(&mheap_.scavengeGoal)
			if retained <= goal {
				break
			}

			// scavengeQuantum is the amount of memory we try to scavenge
			// in one go. A smaller value means the scavenger is more responsive
			// to the scheduler in case of e.g. preemption. A larger value means
			// that the overheads of scavenging are better amortized, so better
			// scavenging throughput.
			const scavengeQuantum = 64 << 10

            // 单次释放足够物理内存,计算耗时。
			start := nanotime()
			r := mheap_.pages.scavenge(scavengeQuantum)
			atomic.Xadduintptr(&mheap_.pages.scav.released, r)
			end := nanotime()

			crit += float64(end - start)
			released += r
		}

        // 本次没能释放内存,阻塞。(手工唤醒)
		if released == 0 {
			scavenge.parked = true
			goparkunlock(&scavenge.lock, waitReasonGCScavengeWait, traceEvGoBlock, 1)
			continue
		}

        // 本次释放足量内存,休眠。(定时器唤醒)
		slept := scavengeSleep(int64(crit / critSleepRatio))

        ...
	}
}

系统监控(sysmon),以及清理结束(finishsweep_m)时,调用 wakeScavenger 唤醒后台操作。

// mgcscavenge.go

// wakeScavenger immediately unparks the scavenger if necessary.
func wakeScavenger() {
    
	if scavenge.parked {
		atomic.Store(&scavenge.sysmonWake, 0)
		stopTimer(scavenge.timer)

		scavenge.parked = false

        // 将 bgscavenge G 放回任务队列,恢复执行。
		var list gList
		list.push(scavenge.g)
		injectglist(&list)
	}
}

注意,每次唤醒操作都会停止计时器。

只有释放量大于 0 时,才会进入定时休眠。期间,会重置定时器,以便再次唤醒。

没有释放量,自然没必要用定时器紧跟着折腾。只能在成功释放时,才有必要启动定时器积极跟进。

// scavengeSleep attempts to put the scavenger to sleep for ns.
func scavengeSleep(ns int64) int64 {

	// 重置定时器。
	start := nanotime()
	resetTimer(scavenge.timer, start+ns)

	// 阻塞。
	scavenge.parked = true
	goparkunlock(&scavenge.lock, waitReasonSleep, traceEvGoSleep, 2)

	return nanotime() - start
}

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

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

发布评论

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