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")
}
 




















