上卷 程序设计
中卷 标准库
- 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
下卷 运行时
源码剖析
附录
文章来源于网络收集而来,版权归原创者所有,如有侵权请及时联系!
4.3.1 G
编译器将 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论