Go-知识panic
- 1. 介绍
- 2. 工作机制
- 2.1 panic函数
- 2.2 工作流程
- 2.3 总结
- 3. 原理
- 3.1 数据结构
- 3.2 gopanic
- 没有defer
- defer函数处理
- 嵌套defer
- 4. 总结
Go-知识error :https://blog.csdn.net/a18792721831/article/details/140430350
Go-知识defer : https://blog.csdn.net/a18792721831/article/details/140734394?spm=1001.2014.3001.5501
1. 介绍
Go 语言开发中程序出现错误,比较常见的是返回error给调用者,但是对于危险的操作,比如内存越界访问等,程序会触发panic,提前结束程序运行。
同样是退出程序,与os.Exit相比,panic的退出方式比较优雅,panic会做一定的善后操作(处理defer函数),并且支持使用recover消除panic。
defer,panic和recover经常会相互作用。
2. 工作机制
程序发生panic时会结束当前协程,进而触发整个程序的崩溃。内置函数recover可以接收panic并使程序重新回到正轨。
触发panic函数中的defer语句是否会执行?
上游函数中的defer语句是否会执行?
其他协程中的defer语句是否会执行?
defer语句中产生panic会发生什么?
2.1 panic函数
panic是一个内置函数
它接收一个任意类型的参数,参数将在程序崩溃时通过另一个内置函数print打印出来,如果程序返回途中任意一个defer函数执行了recover,
那么该参数也是recover的返回值。
panic可由程序员显示触发,Go运行时遇到如内存越界之类的问题时也会触发。
2.2 工作流程
在不考虑recover的情况下,panic的执行如下:
在上面的流程中,黑色箭头代表程序的正常执行流程,红色箭头代表panic的执行流程。
程序启动了两个协程,如果某协程执行过程中产生了panic,那么程序将立即转向执行defer函数,当函数中的defer执行完毕后继续处理上层函数的defer,
当协程中所有defer处理完后,程序退出。
在panic的执行过程中有几点:
- panic会递归执行协程中所有的defer,与函数正常退出时的执行顺序一致
- panic不会处理其他协程中的defer
- 当前协程中的defer处理完成后,触发程序退出
如果panic在执行过程中(defer函数中)再次发生panic,程序将立即中止当前defer函数的执行,然后继续接下来的panic的流程,只是当前defer函数中panic后面的
语句就没有机会执行了。
如果在panic的执行过程中任意一个defer函数执行了recover,那么panic的处理流程就会中止。
2.3 总结
panic触发异常,如果函数没有处理异常,则异常将沿函数调用链逐层向上传递,最终导致程序退出。
每个协程中都维护了一个defer链表,执行过程中每遇到一个defer语句都创建_defer实例并插入链表,函数退出时取出本函数创建的_defer实例并执行。
panic发生时,实际上是把程序流程转向了这个_defer链表,当链表中的defer函数执行完,触发程序退出。
3. 原理
3.1 数据结构
在src/runtime2.go
中定义了_panic的数据结构
当panic运行时,会逐个处理_defer,并且会把当前_panic指针传入_defer中,这样当defer函数中产生新的panic是,会将原panic标记为aborted。
_defer函数中的recover会吧panic标记为recoverd
_panic和_defer都存储在协程数据结构中
3.2 gopanic
panic最终执行的是gopanic函数
func gopanic(e interface{}) {
// 获取 goroutine 的信息
gp := getg()
if gp.m.curg != gp {
print("panic: ")
printany(e)
print("\n")
throw("panic on system stack")
}
// 内存分配的时候出现panic,也就是内存越界
if gp.m.mallocing != 0 {
print("panic: ")
printany(e)
print("\n")
throw("panic during malloc")
}
if gp.m.preemptoff != "" {
print("panic: ")
printany(e)
print("\n")
print("preempt off reason: ")
print(gp.m.preemptoff)
print("\n")
throw("panic during preemptoff")
}
// 处理锁的时候的panic
if gp.m.locks != 0 {
print("panic: ")
printany(e)
print("\n")
throw("panic holding locks")
}
// 创建 _panic 实例
var p _panic
p.arg = e
p.link = gp._panic
gp._panic = (*_panic)(noescape(unsafe.Pointer(&p)))
// 开始处理defer的标记
atomic.Xadd(&runningPanicDefers, 1)
// 循环执行defer
for {
// 获取_defer
d := gp._defer
// 如果_defer没有了,那么跳出循环
if d == nil {
break
}
// 如果是在执行_defer的时候产生了panic,也就是在defer中,再次panic
if d.started {
// 如果defer 的panic不为空,表示在处理defer的panic的defer的时候,又产生了panic,也就是嵌套的时候
// 将嵌套的panic设置为aborted
if d._panic != nil {
d._panic.aborted = true
}
// 否则清空之前的panic
d._panic = nil
d.fn = nil
// 开始执行后续的defer
gp._defer = d.link
// 释放当前defer
freedefer(d)
continue
}
// 标记defer开始执行(panic后执行defer,不嵌套)
d.started = true
// 将_panic传给_defer,用于recover处理
d._panic = (*_panic)(noescape(unsafe.Pointer(&p)))
// panic的参数
p.argp = unsafe.Pointer(getargp(0))
// 执行defer
reflectcall(nil, unsafe.Pointer(d.fn), deferArgs(d), uint32(d.siz), uint32(d.siz))
// 执行完成后,回收 _defer实例
p.argp = nil
// reflectcall did not panic. Remove d.
if gp._defer != d {
throw("bad defer entry in panic")
}
d._panic = nil
d.fn = nil
// 指向下一个defer
gp._defer = d.link
pc := d.pc
sp := unsafe.Pointer(d.sp) // must be pointer so it gets adjusted during stack copy
// 清理_defer, heap-allocated,stack-allocated
freedefer(d)
// 如果在defer中有recover捕获了panic
if p.recovered {
// 退出panic
atomic.Xadd(&runningPanicDefers, -1)
// 移动_panic到下一个
gp._panic = p.link
// 如果panic被中断了,跳过
for gp._panic != nil && gp._panic.aborted {
gp._panic = gp._panic.link
}
// panic列表空
if gp._panic == nil { // must be done with signal
// 设置信号
gp.sig = 0
}
gp.sigcode0 = uintptr(sp)
gp.sigcode1 = pc
// 执行recover
mcall(recovery)
throw("recovery failed") // mcall should not return
}
}
// 打印panic,如果panic是error,调用Error方法获取异常信息,否则调用String方法获取描述
preprintpanics(gp._panic)
// 中止程序
fatalpanic(gp._panic) // should not return
*(*int)(nil) = 0 // not reached
}
没有defer
gopanic函数首先会创建一个_panic实例,并将其保存在协程的_panic链表中,在没有defer需要处理时,会调用fatalpanic函数中止整个程序。
panci的参数信息及函数调用栈就是在fatalpanic函数中打印的。
defer函数处理
gopanic函数会逐个执行defer函数,然后逐个将_defer实例从链表中清除,如果是open-coded类型的defer,还需要额外处理。
嵌套defer
嵌套defer的处理其实并不复杂,当gopanic函数执行某个defer时,如果再次发生panic,
那么程序控制权会交给信的gopanic函数,信的gopanic函数会产生信的_panic实例,并把原_panic实例标记为aborted,然后继续处理剩余的defer函数。
前一个gopanic函数在执行defer时会标记开始状态d.started=true,并把当前_panic实例地址存放在_defer中。
信的gopanic函数再次遍历defer时就可以通过d.started标志判断是否存在被中断的panic,如果有,则把原_panic标记为aborted,然后继续处理剩余defer.
4. 总结
触发panic函数中的defer语句是否会执行? 会
func TestSeven(t *testing.T) {
defer fmt.Println("A")
defer fmt.Println("B")
fmt.Println("C")
panic("panic")
defer fmt.Println("D")
}
上游函数中的defer语句是否会执行? 会
func TestSeven(t *testing.T) {
defer fmt.Println("A")
defer fmt.Println("B")
fmt.Println("C")
panic("panic")
defer fmt.Println("D")
}
func TestEight(t *testing.T) {
defer func() {
recover()
}()
defer func() {
fmt.Println("1")
}()
TestSeven(t)
}
其他协程中的defer语句是否会执行? 不会
func TestSeven(t *testing.T) {
defer fmt.Println("A")
defer fmt.Println("B")
fmt.Println("C")
panic("panic")
defer fmt.Println("D")
}
func TestNine(t *testing.T) {
defer func() {
fmt.Println("Nine")
}()
go TestSeven(t)
time.Sleep(time.Second)
}
defer语句中产生panic会发生什么? 中止当前defer后面的逻辑,接着处理其他的defer
func TestTen(t *testing.T) {
defer func() {
recover()
}()
defer fmt.Println("A")
defer func() {
fmt.Println("B")
panic("inner")
fmt.Println("C")
}()
panic("panic")
defer fmt.Println("D")
}