返回介绍

上卷 程序设计

中卷 标准库

下卷 运行时

源码剖析

附录

4.3.1 G

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

编译器将 go func() 语句翻译为 newproc 调用,目标函数及其参数被打包成 funcval 结构对象。

// test/main.go

func test(x int, p *int) {
	println(x, *p)
}

func main() {
	x := 111;
	y := 222;

	go test(x, &y)
}
$ go build -gcflags "-S"


"".main STEXT size=139 args=0x0 locals=0x20 funcid=0x0 align=0x0

  # 逃逸到堆的 y。
	LEAQ	type.int(SB), AX
	CALL	runtime.newobject(SB)
	MOVQ	AX, "".&y+16(SP)
	MOVQ	$222, (AX)
  
  # 打包目标函数和参数。
	LEAQ	type.noalg.struct { F uintptr; ""..autotmp_2 int; ""..autotmp_3 *int }(SB), AX
	CALL	runtime.newobject(SB)       ; ret AX

  # funcval.fn --> func1 --> test
	LEAQ	"".main.func1(SB), CX
	MOVQ	CX, (AX)                    ; funcval + 0
  
  # funcval.x
	MOVQ	$111, 8(AX)                 ; funcval + 8

  # funcval.p  --> &y
  MOVQ	"".&y+16(SP), CX
	MOVQ	CX, 16(AX)                  ; funcval + 16
  
	CALL	runtime.newproc(SB)


"".main.func1 STEXT size=76 args=0x0 locals=0x18 funcid=0x15 align=0x0
	MOVQ	16(DX), BX   ; p
	MOVQ	8(DX), AX    ; x
	CALL	"".test(SB)
  
  
"".test STEXT size=113 args=0x10 locals=0x20 funcid=0x0 align=0x0
  # x
	MOVQ	AX, ""..autotmp_3+16(SP)
  
  # *p
	MOVQ	(BX), CX
	MOVQ	CX, ""..autotmp_4+8(SP)
  
	MOVQ	""..autotmp_3+16(SP), AX
	CALL	runtime.printint(SB)
  
	MOVQ	""..autotmp_4+8(SP), AX
	CALL	runtime.printint(SB)

了解打包细节后,下面的代码就很好理解了。

// runtime2.go

type funcval struct {
	fn uintptr
	// variable-size, fn-specific data here
}
// proc.go

// Create a new g running fn.
// Put it on the queue of g's waiting to run.
// The compiler turns a go statement into a call to this.

func newproc(fn *funcval) {
	gp := getg()
    
    // 获取 go func 下一指令位置。
	pc := getcallerpc()
    
	systemstack(func() {
		newg := newproc1(fn, gp, pc)

        // 放入本地队列。
		_p_ := getg().m.p.ptr()
		runqput(_p_, newg, true)

        // 如果 main G 启动完毕,则唤醒其他 MP 执行任务。
		if mainStarted {
			wakep()
		}
	})
}

取一个 G 对象(复用或新建),初始化其自带栈,并在 sched 等字段内记录执行相关信息。

// runtime2.go

type g struct {
	stack       stack
	sched       gobuf
	gopc        uintptr  // pc of go statement that created this goroutine
	startpc     uintptr  // pc of goroutine function
}
// proc.go

// Create a new g in state _Grunnable, starting at fn. callerpc is the
// address of the go statement that created this. The caller is responsible
// for adding the new g to the scheduler.

func newproc1(fn *funcval, callergp *g, callerpc uintptr) *g {
    
    // 获取可复用 G,或新建。
	newg := gfget(_p_)
	if newg == nil {
		newg = malg(_StackMin)
		casgstatus(newg, _Gidle, _Gdead)
		allgadd(newg)
	}

    // 初始化任务数据。
	totalSize := uintptr(4*goarch.PtrSize + sys.MinFrameSize) // extra space in case of reads slightly beyond frame
	totalSize = alignUp(totalSize, sys.StackAlign)
	sp := newg.stack.hi - totalSize

	memclrNoHeapPointers(unsafe.Pointer(&newg.sched), unsafe.Sizeof(newg.sched))
	newg.sched.sp = sp
	newg.stktopsp = sp
	newg.sched.pc = abi.FuncPCABI0(goexit) + sys.PCQuantum // +PCQuantum so that previous instruction is in same function
	newg.sched.g = guintptr(unsafe.Pointer(newg))
    
	gostartcallfn(&newg.sched, fn)
	newg.gopc = callerpc
	newg.startpc = fn.fn
    
    // 修改状态,并记入 GC。
    casgstatus(newg, _Gdead, _Grunnable)
	gcController.addScannableStack(_p_, int64(newg.stack.hi-newg.stack.lo))

	return newg
}

编号

在 sched 里有个计数器,用于分配 G.goid 编号。

// runtime2.go

type schedt struct {
    goidgen   uint64
}

考虑到多个 P 共同使用,所以每次都提取一段 "缓存" 到本地。

// proc.go

// Number of goroutine ids to grab from sched.goidgen to local per-P cache at once.
_GoidCacheBatch = 16
// runtime2.go

type p struct {
	// Cache of goroutine ids, amortizes accesses to runtime·sched.goidgen.
	goidcache    uint64
	goidcacheend uint64    
}

type g struct {
	goid         int64    
}

在 newproc1 里,通过判断本地计数是否到达尾部(goidcacheend)来决定是否新取一批过来。

就是简单的将 sched.goidgen + 16,表示取走 16 个连续号。这样就保证了多个 P 之间 G.id 的唯一性。

注意,G 对象复用时,会重新赋予 id。通过编号,我们大概能判断进程里共计创建过多少任务。

// proc.go

func newproc1(fn *funcval, callergp *g, callerpc uintptr) *g {
    
    // 复用或新建。
	newg := gfget(_p_)
	if newg == nil {
		newg = malg(_StackMin)
		casgstatus(newg, _Gidle, _Gdead)
		allgadd(newg) // publishes with a g->status of Gdead so GC scanner doesn't look at uninitialized stack.
	}
    
    // 初始化相关属性 ...
	newg.startpc = fn.fn
    
	casgstatus(newg, _Gdead, _Grunnable)
	gcController.addScannableStack(_p_, int64(newg.stack.hi-newg.stack.lo))

    // 编号缓存不足,从 sched 取一段回来。
	if _p_.goidcache == _p_.goidcacheend {
        
		// Sched.goidgen is the last allocated id,
		// this batch must be [sched.goidgen+1, sched.goidgen+GoidCacheBatch].
		// At startup sched.goidgen=0, so main goroutine receives goid=1.
        
		_p_.goidcache = atomic.Xadd64(&sched.goidgen, _GoidCacheBatch)
		_p_.goidcache -= _GoidCacheBatch - 1
		_p_.goidcacheend = _p_.goidcache + _GoidCacheBatch
	}
    
    // 赋予新编号。
	newg.goid = int64(_p_.goidcache)
	_p_.goidcache++

	return newg
}

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

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

发布评论

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