0%

golang的defer_panic_recover特性

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

1. defer

1.1 匿名和有名返回值(defer能改有名)

  • 如果是匿名返回值,执行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改的这个值,不是返回值
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
  • 有名返回值,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 // 或者直接 return 效果相同
}

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

1.2 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
package main

import (
"fmt"
"time"
)

func main() {
defer P(time.Now())
time.Sleep(1e9) // 1s
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
*/

1.3 调用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() // 什么也不输出
}

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

执行顺序应该是:1. return最先给返回值赋值(有名返回值直接赋值,匿名返回值则先声明再赋值);2. 接着defer开始执行一些收尾工作;3. 最后RET指令携带返回值退出函数。

2. panic

2.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("异常信息")

fmt.Println("c")
}

/*
a
异常信息
b
*/

2.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()
*/

3. recover

3.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
*/
给作者打赏,可以加首页微信,咨询作者相关问题!