defer, panic, recover #
热身样例 #
package main
import (
"fmt"
"time"
)
func main() {
G()
fmt.Println("==============================")
G1()
fmt.Println("==============================")
G2() // will raise panic in goroutine
fmt.Println("==============================")
defer func() {
if err := recover(); err != nil {
fmt.Println("recover from : ", err)
}
}()
defer panic("The third panic")
defer panic("The second panic")
defer panic("The first panic")
panic("main panic")
}
func G() {
defer func() {
fmt.Println("c")
}()
F()
fmt.Println("continue in G()")
}
func F() {
defer func() {
if err := recover(); err != nil {
fmt.Println("capture err in F(): ", err)
}
fmt.Println("b")
}()
panic("panic in F()")
}
func G1() {
defer func() {
if err := recover(); err != nil {
fmt.Println("capture err in G1(): ", err)
}
fmt.Println("c1")
}()
F1()
fmt.Println("continue in G1()")
}
func F1() {
defer func() {
fmt.Println("b1")
}()
panic("panic in F1()")
}
// try to capture from the outside goroutine
func G2() {
defer func() {
if err := recover(); err != nil {
fmt.Println("capture err in G2(): ", err)
}
fmt.Println("c2")
}()
go F2()
fmt.Println("a2")
time.Sleep(time.Second)
}
func F2() {
defer func() {
fmt.Println("b2")
}()
panic("panic in F2()")
}
Actual output:
capture err in F(): panic in F()
b
continue in G()
c
==============================
b1
capture err in G1(): panic in F1()
c1
Defer 原则 #
defer 语句三大规则:
- defer 语句中的变量在“注册”defer 函数时即已经确定。
- defer 的函数以 LIFO 的顺序运行。
- defer 函数可以读写外围函数中预定义的返回值。
看看这个例子,你认为的输出是什么?
func main() {
i := 0
defer fmt.Println("a: ", i) // 0
defer func(i int) {
fmt.Println("b: ", i) // 0
}(i)
defer func() {
fmt.Println("c: ", i) // 1
}()
i++
}
在运行 defer 语句时,Go 会将语句涉及的外部变量全部建立拷贝,因此例子中的 a
为 0 。如果希望规避这个特性,让 defer 中的函数应用外部修改,则可以给 defer 传入匿名函数。虽然函数也是值传递,但传递的是函数指针,因此 c
可以获取到外部修改。
panic:当函数 F 调用 panic
,则 F 的执行会被停止,F 内所有已注册的 defer 函数会被依次执行,最后把 F 返回给它的调用者。
recover:仅在 defer 函数中有效。若当前 goroutine 正在 panic,则调用 recover 会获取到 panic 内容,并继续代码的运行(不会返回 panic 调用点,而是继续运行 defer functions)。若当前 goroutine 一切正常,则只返回 nil
。
总结 #
-
在抛出 panic 的函数中 defer function 能够运行,defer 中的 recover 能够捕获到 panic ,但不会使函数继续运行。
示例代码
func G() { defer func() { fmt.Println("c") // 5 }() F() fmt.Println("continue in G()") // 4 } func F() { defer func() { if err := recover(); err != nil { fmt.Println("capture err in F(): ", err) // 2 } fmt.Println("b") // 3 }() panic("panic in F()") // 1 fmt.Println("after panic") }
-
在抛出 panic 的函数中 defer function 能够运行,且父函数的 defer function 也能够运行,父函数的 defer 中 recover 能够捕获到 panic 并恢复,但不会继续运行。
示例代码
func G1() { defer func() { if err := recover(); err != nil { fmt.Println("capture err in G1(): ", err) // 4 } fmt.Println("c1") // 5 }() F1() // 3 - get a panic in here fmt.Println("continue in G1()") } func F1() { defer func() { fmt.Println("b1") // 2 }() panic("panic in F1()") // 1 fmt.Println("after panic") }
-
若在 goroutine 协程抛出 panic,则外部函数无法捕获到协程内抛出的 panic。
示例代码
// try to capture from the outside goroutine func G2() { defer func() { if err := recover(); err != nil { fmt.Println("capture err in G2(): ", err) } fmt.Println("c2") }() go F2() fmt.Println("a2") // 1 time.Sleep(time.Second) } func F2() { defer func() { fmt.Println("b2") // 3 }() panic("panic in F2()") // 2 }
-
若 goroutine 中出现 panic 后,defer 函数又继续 panic,则最后的 recover 函数捕获的是最后一次 panic 的信息。
示例代码
defer func() { if err := recover(); err != nil { fmt.Println("recover from : ", err) // 5 } fmt.Println("end") // 6 if err := recover(); err != nil { fmt.Println("recover from : ", err) } }() defer panic("The third panic") // 4 defer panic("The second panic") // 3 defer panic("The first panic") // 2 panic("main panic") // 1 fmt.Println("after main panic")