golang-design / under-the-hood

📚 Go: Under The Hood | Go 语言原本 | https://golang.design/under-the-hood

Home Page:https://golang.design/under-the-hood

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

book: 改进 ch06sched 中 gfget/gfput 的描述

luckyxiaoqiang opened this issue · comments

实际描述

  • 文件路径:book/zh-cn/part2runtime/ch06sched/mpg.md
  • 原文段落:
    (关于是否需要新分配栈的描述部分)
// 从 gfree 链表中获取 g
// 如果 P 本地 gfree 链表为空,从调度器的全局 gfree 链表中取
func gfget(_p_ *p) *g {
retry:
	if _p_.gFree.empty() && (!sched.gFree.stack.empty() || !sched.gFree.noStack.empty()) {
		lock(&sched.gFree.lock)
		// 将一批空闲的 G 移动到 P
		for _p_.gFree.n < 32 {
			// 倾向于有栈的 G
			gp := sched.gFree.stack.pop()
			if gp == nil {
				gp = sched.gFree.noStack.pop()
				if gp == nil {
					break
				}
			}
			sched.gFree.n--
			_p_.gFree.push(gp)
			_p_.gFree.n++
		}
		unlock(&sched.gFree.lock)
		goto retry
	}
	gp := _p_.gFree.pop()
	if gp == nil {
		return nil
	}
	// 拿到一个 g
	_p_.gFree.n--
	// 查看是否需要分配运行栈
	if gp.stack.lo == 0 {
		// 栈可能从全局 gfree 链表中取得,栈已被 gfput 给释放,所以需要分配一个新的栈。
		// 栈分配发生在系统栈上
		systemstack(func() {
			gp.stack = stackalloc(_FixedStack)
		})
		// 计算栈边界
		gp.stackguard0 = gp.stack.lo + _StackGuard
	}
	(...)
	return gp
}

总结一下整个过程,gFree 用来表示已经执行完毕那些 g 对象,在 P 和调度器中均有保存,目的很明显是复用:

  1. 首先从 P 的 gFree 链表中取;
  2. 如果从 P 的 gFree 链表中取不到,再看从调度器的 gfree 链表取;
    • 首先倾向于获取已经有执行栈的 g,因为省去了执行栈的获取
    • 否则才去取没有执行栈的队列
    • 如果都找不到则确实找不到可以复用的 g 了;
  3. 无论如何,如果找到了,则从 gfree 链表中取一个 g,这时 g 可能是从调度器的 gfree 中取出的没有执行栈的 g,因此按需创建

预期描述

// 从 gfree 链表中获取 g
// 如果 P 本地 gfree 链表为空,从调度器的全局 gfree 链表中取
func gfget(_p_ *p) *g {
retry:
	if _p_.gFree.empty() && (!sched.gFree.stack.empty() || !sched.gFree.noStack.empty()) {
		lock(&sched.gFree.lock)
		// 将一批空闲的 G 移动到 P
		for _p_.gFree.n < 32 {
			// 倾向于有栈的 G
			gp := sched.gFree.stack.pop()
			if gp == nil {
				gp = sched.gFree.noStack.pop()
				if gp == nil {
					break
				}
			}
			sched.gFree.n--
			_p_.gFree.push(gp)
			_p_.gFree.n++
		}
		unlock(&sched.gFree.lock)
		goto retry
	}
	gp := _p_.gFree.pop()
	if gp == nil {
		return nil
	}
	// 拿到一个 g
	_p_.gFree.n--
	// 查看是否需要分配运行栈
	if gp.stack.lo == 0 {
		// 栈可能是非固定大小,已被 gfput 给释放,所以需要分配一个新的栈。
		// 栈分配发生在系统栈上
		systemstack(func() {
			gp.stack = stackalloc(_FixedStack)
		})
		// 计算栈边界
		gp.stackguard0 = gp.stack.lo + _StackGuard
	}
	(...)
	return gp
}

总结一下整个过程,gFree 用来表示已经执行完毕那些 g 对象,在 P 和调度器中均有保存,目的很明显是复用:

  1. 首先从 P 的 gFree 链表中取;
  2. 如果从 P 的 gFree 链表中取不到,再看从调度器的 gfree 链表取;
    • 首先倾向于获取已经有执行栈的 g,因为省去了执行栈的获取
    • 否则才去取没有执行栈的队列
    • 如果都找不到则确实找不到可以复用的 g 了;
  3. 无论如何,如果找到了,则从 gfree 链表中取一个 g,取到的 g 的栈可能由于此前为非固定大小,已被 gfput 给释放,所以需要分配新的栈。

附图