GO成神之路: Go异常处理(二)|Go主题月

962 阅读2分钟

Go异常处理的的用法

go中异常处理用法是defer...recover...panic组合

panic用于抛出异常,recover用于拦截未处理的panic,panic会导致函数退出,函数推出前会执行defer定义的函数,从而导致panicrecover函数拦截

package main

import "log"

func main() {
	defer func() {
		if err := recover(); err != nil {
			log.Println(err)
		}
	}()
	test()
}

func test(){
	panic("error")
}

panic的实现原理

panic被编译后对应的是runtime.gopanic,代码如下:

// gopanic接收一个参数,这个参数就是通过panic函数传递的参数
func gopanic(e interface{}) {
        
	gp := getg()
	// 获取g并对g做一些可预知的错误判断
        // ...
        
        ///下面创建一个_panic
	var p _panic
	p.arg = e
	p.link = gp._panic
        // 将panic放在g中的_panic链表头部
	gp._panic = (*_panic)(noescape(unsafe.Pointer(&p)))
       
        // 当发生panic时,会立即处理当前g的_defer队列
	for {
		d := gp._defer
                // 如果不存在defer则跳出
		if d == nil {
			break
		}

		//如果存在defer则判断defer是否开始执行
		if d.started {
			if d._panic != nil {
				d._panic.aborted = true
			}
			d._panic = nil
			if !d.openDefer {
				// For open-coded defers, we need to process the
				// defer again, in case there are any other defers
				// to call in the frame (not including the defer
				// call that caused the panic).
				d.fn = nil
				gp._defer = d.link
				freedefer(d)
				continue
			}
		}
                // 标记为开始状态
		d.started = true

		// 将g中的panic复制给当前defer,从以上代码可以看出,panic会为每一个defer设置当前panic
		d._panic = (*_panic)(noescape(unsafe.Pointer(&p)))
                
		done := true
                
                // 无论怎样都会调用defer定义的函数
		if d.openDefer {
			done = runOpenDeferFrame(gp, d)
			if done && !d._panic.recovered {
				addOneOpenDeferFrame(gp, 0, nil)
			}
		} else {
			p.argp = unsafe.Pointer(getargp(0))
			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
                // 将gp中的_deger指向_defer链表上的下一个deger对象
		if done {
			d.fn = nil
			gp._defer = d.link
			freedefer(d)
		}
                // 判断panic是否被处理,如果被处理则销毁panic
		if p.recovered {
			// 退出gopanic函数并回到正常的函数调用
			mcall(recovery)
                        // 如果mcall会返回到这里,说明存在错误,则调用throw抛出致命错误导致调用exit()函数
			throw("recovery failed") // mcall should not return
		}
	}

	// 打印错误堆栈信息
	preprintpanics(gp._panic)
        // 致命错误,调用exit()退出
	fatalpanic(gp._panic) // should not return
	*(*int)(nil) = 0      // not reached
}
  1. 编译器将panic编译为runtime.gopanic函数
  2. 在gopanic函数中,将调用defer链中对应的函数
  3. 如果存在recover来处理当前panic则嗲用mcall函数回到正常的函数调用中
  4. 如果所有的defer链被执行完成后还是没有处理panic则调fatalpanic函数
  5. fatalpanic会导致程序退出,因为它内部调用了exit

从上面代码的分析,至少可以得出以下结论

  1. panic会触发defer链被调用
  2. defer链始终会被完全调用