每次都被 defer
,panic
和recover
坑的死去活来,今天抽出时间来整理一下。
1. defer 返回值
defer的设计与C++的析构函数和Java中的finalize方法或其他语言类似功能等有异曲同工之妙,都是为了在主执行逻辑结束之前做一些收尾工作(如资源回收、逻辑状态重置、业务闭环等)。
defer 和 return 执行顺序(defer永远在return有名返回值后面搞事情)
return最先给返回值赋值(有名返回值直接赋值,匿名返回值则先声明再赋值)
接着defer开始执行一些收尾工作
最后RET指令携带返回值退出函数
1.1 匿名返回值
- 如果是匿名返回值,执行Return语句后,Go会创建一个临时变量保存返回值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| package main
import ( "fmt" )
func main() { fmt.Println("a return:", a()) }
func a() int { var i int defer func() { i++ fmt.Println("a defer2:", i) }() defer func() { i++ fmt.Println("a defer1:", i) }() return i }
|
1.2 有名返回值
有名返回值,defer能看到
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| package main
import ( "fmt" )
func main() { fmt.Println("b return:", b()) }
func b() (i int) { defer func() { i++ fmt.Println("b defer2:", i) }() defer func() { i++ fmt.Println("b defer1:", i) }() return i }
|
2. defer 注意点
2.1 defer 参数会提前算
defer声明时会先计算确定参数的值,defer推迟执行的仅是其函数体。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| package main
import ( "fmt" "time" )
func main() { defer P(time.Now()) time.Sleep(time.second) fmt.Println("1", time.Now()) } func P(t time.Time) { fmt.Println("2", t) fmt.Println("3", time.Now()) }
|
2.2 调用os.Exit不会执行defer
当发生panic时,所在goroutine的所有defer会被执行,但是当调用os.Exit()方法退出程序时,defer并不会被执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| package main
import ( "fmt" "os" )
func deferExit() { defer func() { fmt.Println("defer") }() os.Exit(0) } func main() { deferExit() }
|
2.3 defer面试题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| package main
import "fmt"
func DeferFunc1(i int) (t int) { t = i defer func() { t += 3 }() return t }
func DeferFunc2(i int) int { t := i defer func() { t += 3 }() return t }
func DeferFunc3(i int) (t int) { defer func() { t += i }() return 2 }
func DeferFunc4() (t int) { defer func(i int) { fmt.Println(i) fmt.Println(t) }(t) t = 1 return 2 }
func main() { fmt.Println(DeferFunc1(1)) fmt.Println(DeferFunc2(1)) fmt.Println(DeferFunc3(1)) DeferFunc4() }
|
2.4 头脑风暴
- 有名返回值和匿名返回值,defer只能改有名返回值。
- 只记住一点也可以:defer永远在return有名返回值后面搞事情。
3. panic
3.1 截获 panic
- defer 和 recover 配合, 并且先声明defer
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| package main
import ( "fmt" )
func main() { defer func() { fmt.Println("a") if err := recover(); err != nil { fmt.Println(err) } fmt.Println("b") }()
panic("异常信息")
defer func(){fmt.Println("defer:panic之后,永远执行不到")}() }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| package main
import "fmt"
func main() { defer func() { if err := recover(); err != nil { fmt.Println(err)
} else { fmt.Println("fatal") } }()
defer func() { panic("defer panic") }()
panic("panic") }
|
panic仅有最后一个可以被recover捕获。触发panic("panic")后defer按顺序出栈执行,第1个被执行的defer中会有panic("defer panic")异常语句,这个异常将会覆盖main中的异常panic("panic"),最后这个异常被第2个执行的defer捕获。
3.2 panic 传递
panic 会一直传递, 导致主 gorouting 奔溃
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| package main
import ( "fmt" "time" )
func testPanic2() { panic("testPanic panic2") }
func testPanic1() { go testPanic2() }
func main() { fmt.Println("begin") go testPanic1() for { time.Sleep(time.Second) } }
|
4. recover
4.1 截获 panic 只能在一个层
外层的 recover 能捕捉里层的 panic吗? 不能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| package main
import ( "fmt" "time" )
func main() { defer func() { if err := recover(); err != nil { fmt.Println(err) } }()
go func() { panic("falut1") }()
time.Sleep(time.Second) }
|