1. defer简单介绍
defer关键字后的函数或则和方法想要执行必须先注册,return或者panic之后的defer是不能注册的,这是大前提.
Go语言的函数中return语句在底层并不是原子操作,它分为给返回值赋值和RET指令两步.而defer语句的执行的时机就在返回值赋值操作后,RET指令前.
1.1 defer函数执行时机
func calc(index string, a, b int) int {
ret := a + b
fmt.Println(index, a, b, ret)
return ret
}
func main() {
x := 1
y := 2
defer calc("AA", x, calc("A", x, y))
x = 10
defer calc("BB", x, calc("B", x, y))
y = 20
}
A 1 2 3
B 10 2 12
BB 10 12 22
AA 1 3 4
type Slice []int
func NewSlice() Slice {
return make(Slice, 0)
}
func (s *Slice) Add(elem int) *Slice {
*s = append(*s, elem)
fmt.Print(elem)
return s
}
func main() {
s := NewSlice()
defer s.Add(1).Add(2)
s.Add(3)
}
//132
defer后面的函数如果携带有参数(包括接收者),会优先计算参数,并将结果存储在栈中,到真正执行defer()的时候取出.并且defer在执行时,是先进后出的.即靠后的defer会先执行.
1.2 defer中函数的具名返回值和匿名返回值的区别
defer注册要延迟执行的函数时该函数所有的参数都需要确定其值
func main() {
fmt.Println("f1 result: ", f1())
/*
f12 1001
f11 1002
f1 1000
*/
fmt.Println("f2 result: ", f2())
/*
f22 1001
f21 1002
f2 1002
*/
}
func f1() int {
var i int
defer func() {
i++
fmt.Println("f11: ", i)
}()
defer func() {
i++
fmt.Println("f12: ", i)
}()
i = 1000
return i
}
func f2() (i int) {
defer func() {
i++
fmt.Println("f21: ", i)
}()
defer func() {
i++
fmt.Println("f22: ", i)
}()
i = 1000
return i
}
原因就是return会将返回值先保存起来,对于无名返回值来说, 保存在一个临时对象中,defer是看不到这个临时对象的; 而对于有名返回值来说,就保存在已命名的变量中。
1.3 defer注册函数时机
func f(n int) (r int) {
defer func() {
r += n // 9
recover()
}()
var f1 func()
f1 = func() {
fmt.Println("f1被调用了")
r += 2 // //r = 6
}
defer f1()
return n + 1 // r = 4
}
func main() {
fmt.Println(f(3))
}
//9
func f(n int) (r int) {
defer func() {
r += n // 7
if err := recover(); err != nil {
fmt.Println("异常")
}
}()
var f1 func()
defer f1()
f1 = func() {
fmt.Println("f1被调用了")
r += 2
}
return n + 1 // r = 4
}
func main() {
fmt.Println(f(3))
}
// 7
第二个情况下,defer在注册f1时,由于f1()未定义,发生panic,第二个defer中的recover执行.
2.Go协程中的panic问题
在Go语言中,存在多个协程的情况下,单个协程触发panic会导致其它所有协程挂掉,每个协程只能捕获到自己的panic,不能捕获其它协程.
func main() {
// 协程 A
go func() {
for {
fmt.Println("协程 A")
}
}()
// 协程 B
go func() {
time.Sleep(1 * time.Microsecond) // 确保 协程 A 先运行起来
panic("协程 B panic")
}()
time.Sleep(10 * time.Second) // 充分等待协程 B 触发 panic 完成和协程 A 执行完毕
fmt.Println("main end")
}
协程 A
协程 A
协程 A
协程 A
panic: 协程 B panic
goroutine 20 [running]:
main.main.func2()
D:/softwore/GoRoute/GoAlgorithm/leecode/main.go:19 +0x2b
created by main.main in goroutine 1
D:/softwore/GoRoute/GoAlgorithm/leecode/main.go:17 +0x26
从结果来看,B触发panic之后,不仅协程A停止了,而且主协程也挂掉了.
func main() {
// 协程 A
go func() {
panic("协程A panic")
}()
defer func() {
if err := recover(); err != nil {
fmt.Printf("panic err is ", err)
}
}()
time.Sleep(10 * time.Second) // 充分等待协程 B 触发 panic 完成和协程 A 执行完毕
fmt.Println("main end")
}
panic: 协程A panic
goroutine 6 [running]:
main.main.func1()
D:/softwore/GoRoute/GoAlgorithm/leecode/main.go:11 +0x25
created by main.main in goroutine 1
D:/softwore/GoRoute/GoAlgorithm/leecode/main.go:10 +0x2a
从结果来看,主协程无法recover到协程A的panic.哪个协程发生panic,就需要在哪个协程(发生panic之前)自身中recover.并且recover只能放在 defer后面的函数中执行.