在golang当中,defer代码块会在函数调用链表中增加一个函数调用。这个函数调用不是普通的函数调用,而是会在函数正常返回,也就是return之后添加一个函数调用。因此,defer通常用来释放函数内部变量。 通过defer,我们可以在代码中优雅的关闭/清理代码中所使用的变量。defer作为golang清理变量的特性,有其独有且明确的行为。
defer特性:在函数返回之前,调用defer函数的操作,简化函数的清理工作。
func a() { i := 0 defer fmt.Println(i) // 输出的是0,因为i=0,已经明确告诉golang在程序退出时执行输出0的操作 i++ defer fmt.Println(i) // 输出的是1 return }
输出结果是 1 0
func b() { for i := 0; i < 4; i++ { defer fmt.Print(i) } // 输出结果是 3210,即LIFO。 } func c() int { var i int defer func() { i++ fmt.Println("a defer2:", i) // 输出结果为 a defer2: 2 }() defer func() { i++ fmt.Println("a defer1:", i) // 输出结果为 a defer1: 1 }() return i }
同个函数的defer遵循后进先出
func d() (result int) { defer func() { result++ }() return 0 // 返回值是1,在defer中被更改了 } func e() (r int) { t := 5 r = t defer func() { t = t + 5 }() return t // 返回值是5,在defer中并没有修改r的值 } func f() (r int) { defer func(r int) { r = r + 5 }(r) return 1 } func g() (r int) { defer func(r *int) { *r = *r + 5 }(&r) return 1 // 返回值是6,defer的传入参数是引用类型,取地址操作会改变最终r的值 }
二、panic用法
Panic is a built-in function that stops the ordinary flow of control and begins panicking. When the function F calls panic, execution of F stops, any deferred functions in F are executed normally, and then F returns to its caller. To the caller, F then behaves like a call to panic. The process continues up the stack until all functions in the current goroutine have returned, at which point the program crashes. Panics can be initiated by invoking panic directly. They can also be caused by runtime errors, such as out-of-bounds array accesses.
panic是内建函数,会中断函数F的正常执行流程,从F函数中跳出来,跳回到F函数的调用者。对于调用者来说, F看起来就是一个panic,所以调用者会继续向上跳出,直到当前goroutine返回。在跳出的过程中,进程会保持这个函数栈。当goroutine退出时,程序会crash。
除了主动调用panic之外,任何运行时错误都会造成panic。
func TestPanic(t *testing.T) { panicTest() } func panicTest() { defer func() { fmt.Println(1) }() defer func() { fmt.Println(2) }() panic("手动触发异常") fmt.Println("触发异常,将无法执行") }
运行结果如下: === RUN TestPanic 2 1 --- FAIL: TestPanic (0.00s) panic: 手动触发异常 [recovered] panic: 手动触发异常 goroutine 6 [running]: testing.tRunner.func1.1(0x6ede00, 0x741540) E:/Program Files (x86)/go/src/testing/testing.go:1072 +0x310 testing.tRunner.func1(0xc00002b080)
三、recover用法
Recover is a built-in function that regains control of a panicking goroutine. Recover is only useful inside deferred functions. During normal execution, a call to recover will return nil and have no other effect. If the current goroutine is panicking, a call to recover will capture the value given to panic and resume normal execution.
recover也是一个内建函数,用于捕捉程序异常,必须与defer函数配合使用,通常做法是recover返回非nil时,出现错误,用于执行程序清理资源操作。
func TestRecover(t *testing.T) { recoverTest() } func recoverTest() { defer func() { if err := recover(); err != nil { fmt.Println("recover捕获到panic") fmt.Println(err) } }() fmt.Println("recoverTest运行开始") panic("运行出现异常") }
--运行结果 === RUN TestRecover recoverTest运行开始 recover捕获到panic 运行出现异常 --- PASS: TestRecover (0.00s) PASS 进程 已完成,退出代码为 0