上卷 程序设计
中卷 标准库
- 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
下卷 运行时
源码剖析
- 1. 初始化
- 2. 内存分配
- 3. 垃圾回收
- 4. 并发调度
- 5. 通道
- 6. 延迟调用
- 7. 终结器
- 8. 其他
附录
6.2 panic
编译器将 panic 翻译成 gopanic 调用。
package main func test() { recover() } func main() { defer test() panic("xxx") }
$ go tool objdump -s "main\.main" test TEXT main.main(SB) test.go test.go:9 0x1056fd6 CALL runtime.gopanic(SB) $ go tool objdump -s "main\.test" test TEXT main.test(SB) test.go test.go:4 0x1056f66 CALL runtime.gorecover(SB)
gopanic
和 defer 类似,panic 也会保存到 G 里。
// runtime2.go type _panic struct { argp unsafe.Pointer // pointer to arguments of deferred call run during panic arg interface{} // argument to panic link *_panic // link to earlier panic pc uintptr // where to return to in runtime if this panic is bypassed sp unsafe.Pointer // where to return to in runtime if this panic is bypassed recovered bool // whether this panic is over aborted bool // the panic was aborted goexit bool }
type g struct { _panic *_panic }
引发错误前,需确保 G 已注册的延迟函数得以执行。
仅最后一个 panic 抛出。
但所有 panic 都保存在 G._panic 链表,以便输出跟踪(traceback)信息。
// panic.go // The implementation of the predeclared function panic. func gopanic(e interface{}) { gp := getg() // 创建,添加到 G._panic 链表头部。 var p _panic p.arg = e p.link = gp._panic gp._panic = (*_panic)(noescape(unsafe.Pointer(&p))) // 确保已注册延迟调用被执行。 for { d := gp._defer if d == nil { break } // If defer was started by earlier panic or Goexit (and, since we're back here, that triggered a new panic), // take defer off list. An earlier panic will not continue running, but we will make sure below that an // earlier Goexit does continue running. // 延迟函数(defer fn)可再次调用 panic,那么 gopanic 会 “递归嵌套”。 // 在内层,d 已被外层 panic 或 goexit 启动。跳过,并将其从链表移除。 // 由内层处理掉剩余的,且不会返回(not return)外层。 // 结果要么崩溃(fatalpanic),要么 recover 回到 caller 函数。 // 如果当前 defer 已经执行,则清理掉。 if d.started { if d._panic != nil { d._panic.aborted = true } d._panic = nil // 释放当前 defer,继续执行下一个。 if !d.openDefer { d.fn = nil gp._defer = d.link freedefer(d) continue } } // 启动当前 defer,并绑定(供内层检查)。 d.started = true d._panic = (*_panic)(noescape(unsafe.Pointer(&p))) done := true // 执行延迟调用。 p.argp = unsafe.Pointer(getargp(0)) // gopainc sp reflectcall(nil, unsafe.Pointer(d.fn), deferArgs(d), uint32(d.siz), uint32(d.siz)) p.argp = nil d._panic = nil // 延迟调用(deferproc)保存的 caller 数据。 pc := d.pc sp := unsafe.Pointer(d.sp) // 丛链表删除。 if done { d.fn = nil gp._defer = d.link freedefer(d) } // 如果延迟调用内执行了 recover 函数。 // 那么 gopainc 执行到此结束,由 recover 恢复 caller 调用状态。 // 剩余工作就交给 caller deferreturn 了。 if p.recovered { // 本次 panic 被捕获,从链表移除。 gp._panic = p.link // 将放弃的 panic 从链表移除。 for gp._panic != nil && gp._panic.aborted { gp._panic = gp._panic.link } // 恢复 caller 调用。 // 当前 gopanic 被抛弃,不再返回。 gp.sigcode0 = uintptr(sp) gp.sigcode1 = pc mcall(recovery) throw("recovery failed") // mcall should not return } } // 输出错误信息,进程崩溃。 preprintpanics(gp._panic) fatalpanic(gp._panic) // should not return *(*int)(nil) = 0 // not reached }
fatalpanic 调用 os.exit 终止进程,不会等待其他 goroutine,也不会执行其他 G.defers。
因为 panic 代表不可修复错误,如果要等待其他 goroutine,则可能永远无法终止进程。那就失去了 panic 的设计初衷。
gorecover
在延迟函数内调用 recover,被编译器转换为 gorecover 调用。
仅是标记,还是由 gopanic 调用 recovery 处理。
参数 argp 由延迟函数传入,指向 defer caller.SP。
// The implementation of the predeclared function recover. func gorecover(argp uintptr) interface{} { // Must be in a function running as part of a deferred call during the panic. // Must be called from the topmost function of the call // (the function used in the defer statement). // p.argp is the argument pointer of that topmost deferred function call. // Compare against argp reported by caller. // If they match, the caller is the one who can recover. gp := getg() p := gp._panic // 通过比对 caller.SP 地址,确保在 topmost 内调用。 if p != nil && !p.goexit && !p.recovered && argp == uintptr(p.argp) { p.recovered = true return p.arg } return nil }
recovery
将调用堆栈恢复到 deferproc 下一条指令。
前面 gopanic 用 G.sigcode 传递 defer 保存的 caller SP、PC 值。
但要知道,这两个值是调用 deferproc 时候的状态,也就是说 PC 并非指向函数尾部。
那么,gogo 实际执行的是 deferproc 下一条指令。
在 recovery 里,设置
sched.ret = 1
,gogo 先将其放到 AX。编译器在 deferproc 调用下方生成:
CALL runtime.deferproc(SB)
TESTL AX, AX // if AX == 0 then ZF = 1 else ZF = 0.
JNE 83 // if ZF == 0 then jmp.
...
XCHGL AX, AX // 83
CALL runtime.deferreturn(SB)
如此,可以确保后续 defer 被 deferreturn 执行。
func recovery(gp *g) { sp := gp.sigcode0 pc := gp.sigcode1 gp.sched.sp = sp gp.sched.pc = pc gp.sched.lr = 0 gp.sched.ret = 1 gogo(&gp.sched) }
func deferproc(siz int32, fn *funcval) { // arguments of fn follow fn ... // deferproc returns 0 normally. // a deferred func that stops a panic // makes the deferproc return 1. // the code the compiler generates always // checks the return value and jumps to the // end of the function if deferproc returns != 0. return0() // No code can go here - the C return register has // been set and must not be clobbered. }
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论