0%

golang的defer和panic_recover特性

每次都被 deferpanicrecover 坑的死去活来,今天抽出时间来整理一下。

1. defer 返回值

defer的设计与C++的析构函数和Java中的finalize方法或其他语言类似功能等有异曲同工之妙,都是为了在主执行逻辑结束之前做一些收尾工作(如资源回收、逻辑状态重置、业务闭环等)。

defer 和 return 执行顺序(defer永远在return有名返回值后面搞事情)

  1. return最先给返回值赋值(有名返回值直接赋值,匿名返回值则先声明再赋值)

  2. 接着defer开始执行一些收尾工作

  3. 最后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
}

// 打印结果为 a defer1: 1
// 打印结果为 a defer2: 2
// 打印结果为 a return: 0

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
}

// 打印结果为 b defer1: 1
// 打印结果为 b defer2: 2
// 打印结果为 b return: 2

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())
}

/*
1 2020-06-30 20:27:58.893326
2 2020-06-30 20:27:57.892902
3 2020-06-30 20:27:58.89402
*/

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) {//有名返回值,折腾下,4
t = i
defer func() {
t += 3
}()
return t
}

func DeferFunc2(i int) int {//匿名改不了, 1
t := i
defer func() {
t += 3
}()
return t
}

func DeferFunc3(i int) (t int) {// 有名返回值,折腾下 3
defer func() {
t += i
}()
return 2
}

func DeferFunc4() (t int) {
defer func(i int) {
fmt.Println(i) // 提前入了参数
fmt.Println(t) // return后面了
}(t)
t = 1
return 2
}

func main() {
fmt.Println(DeferFunc1(1)) // 4
fmt.Println(DeferFunc2(1)) // 1
fmt.Println(DeferFunc3(1)) // 3
DeferFunc4() // 0 2
}

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() { // 必须要先声明defer,否则不能捕获到panic异常
fmt.Println("a")
if err := recover(); err != nil {
fmt.Println(err)
}
fmt.Println("b")
}()

panic("异常信息")

defer func(){fmt.Println("defer:panic之后,永远执行不到")}()
}

/*
a
异常信息
b
*/
  • 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")
}

// 输出defer 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)
}
}


/*
begin
panic: testPanic panic2

goroutine 5 [running]:
main.testPanic2()
*/

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)
}

/*
panic: falut1

goroutine 18 [running]:
main.main.func2()
/Users/liuwei/golang/src/golangTest/suanfa/main.go:16 +0x39
created by main.main
/Users/liuwei/golang/src/golangTest/suanfa/main.go:15 +0x71
exit status 2
*/
可以加首页作者微信,咨询相关问题!