defer介绍

一.defer 介绍
定义:

A defer statement pushes a function call onto a list. The list of saved calls is executed after the surrounding function returns. Defer is commonly used to simplify functions that perform various clean-up actions.

defer语句把函数push到一个list中,这个List中保存的函数将在父函数返回后调用。defer通常用来简化执行clean-up动作。

refer: https://blog.golang.org/defer-panic-and-recover

二.defer 入门example
example.1 折叠源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func CopyFile(dstName, srcName string) (written int64, err error) {
src, err := os.Open(srcName)
if err != nil {
return
}

dst, err := os.Create(dstName)
if err != nil {
    return
}

written, err = io.Copy(dst, src)
dst.Close()
src.Close()
return

}
example.2 折叠源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func CopyFile(dstName, srcName string) (written int64, err error) {
src, err := os.Open(srcName)
if err != nil {
return
}
defer src.Close()

dst, err := os.Create(dstName)
if err != nil {
    return
}
defer dst.Close()

return io.Copy(dst, src)

}

三.defer三原则

  1. A deferred function’s arguments are evaluated when the defer statement is evaluated.

rule.1 折叠源码
1
2
3
4
5
6
func a() {
i := 0
defer fmt.Println(i)
i++
return
}
rule.1.output 展开源码

  1. Deferred function calls are executed in Last In First Out order after the surrounding function returns.

rule.2 折叠源码
1
2
3
4
5
func b() {
for i := 0; i < 4; i++ {
defer fmt.Print(i)
}
}
rule.2.output 折叠源码
1
2
3
4
5
output:
3210

defer函数的调用采用LIFO方式

  1. Deferred functions may read and assign to the returning function’s named return values.

rule.3 折叠源码
1
2
3
4
func c() (i int) {
defer func() { i++ }()
return 1
}
rule.3.output 折叠源码
1
2
3
4
5
output:
2

defer函数可以读、修改父函数的返回值

四.defer使用场景
1.简化且保证资源释放

a. 锁

usage-scenario:lock 折叠源码
1
2
3
4
5
6
7
8
9
func set(mu *sync.Mutex, arr []int, i, v int) {

mu.Lock()

arr[i] = v

mu.Unlock()

}
usage-scenario:lock 折叠源码
1
2
3
4
5
6
7
8
func set(mu *sync.Mutex, arr []int, i, v int) {

mu.Lock()
defer mu.Unlock()

arr[i] = v

}
如果 i >= len(arr)的话, runtime就会抛出切片越界的异常. 这样的话, mu.Unlock() 就没有机会被执行了.
b. 打开的文件

usage-scenario:file 折叠源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func CopyFile(dstName, srcName string) (written int64, err error) {
src, err := os.Open(srcName)
if err != nil {
return
}
defer src.Close()

dst, err := os.Create(dstName)
if err != nil {
    return
}
defer dst.Close()

return io.Copy(dst, src)

}
2.panic异常捕获

a. 结合recover做异常恢复

b. 异常时的资源释放

Panic is a built-in function that stops the ordinary flow of control and begins panicking. When the function F calls panic, execution of F stops, any deferred functions in F are executed normally, and then F returns to its caller. To the caller, F then behaves like a call to panic. The process continues up the stack until all functions in the current goroutine have returned, at which point the program crashes. Panics can be initiated by invoking panic directly. They can also be caused by runtime errors, such as out-of-bounds array accesses.

Recover is a built-in function that regains control of a panicking goroutine. Recover is only useful inside deferred functions. During normal execution, a call to recover will return nil and have no other effect. If the current goroutine is panicking, a call to recover will capture the value given to panic and resume normal execution.

panic用于抛出异常, recover用于捕获异常.。即便函数抛出了异常,defer也会被执行的。这样就不会因程序出现了错误,而导致资源不会释放了。panic可能是用户自己调用panic(),也可能是runtime错误触发,例如数组越界。

recover只能在defer语句中使用, 直接调用recover是无效的.如果当前goroutine正在抛异常,recover会捕获panic的值并进行恢复。

usage-scenario:panic-recover 折叠源码
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
package main

import “fmt”

func main() {
f()
fmt.Println(“Returned normally from f.”)
}

func f() {
defer func() {
if r := recover(); r != nil {
fmt.Println(“Recovered in f”, r)
}
}()
fmt.Println(“Calling g.”)
g(0)
fmt.Println(“Returned normally from g.”)
}

func g(i int) {
if i > 3 {
fmt.Println(“Panicking!”)
panic(fmt.Sprintf("%v", i))
}
defer fmt.Println(“Defer in g”, i)
fmt.Println(“Printing in g”, i)
g(i + 1)
}
usage-scenario:panic-recover.output 折叠源码
1
2
3
4
5
6
7
8
9
10
11
12
13
output:
Calling g.
Printing in g 0
Printing in g 1
Printing in g 2
Printing in g 3
Panicking!
Defer in g 3
Defer in g 2
Defer in g 1
Defer in g 0
Recovered in f 4
Returned normally from f.
3.延迟执行用户自定义方法

usage-scenario:costum-func 折叠源码
1
2
3
4
5
6
func costum() {
printHeader()


defer printFooter()
}

4.修改返回值

usage-scenario:modify-return 折叠源码
1
2
3
4
5
6
7
8
9
10
11
12
13
func doubleSum(a, b int) (sum int) {

defer func() {   //该函数在函数返回时  调用

    sum *= 2

}()

sum = a + b
return sum
}

fmt.Println(doubleSum(1, 2))
usage-scenario:modify-return.output 折叠源码
1
2
output:
6

四.defer trap
4.1 返回值trap

trap.1 折叠源码
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
package main

import (
“fmt”
)

func f() (result int) {
defer func() {
result++
}()
return 0
}

func g() (r int) {
t := 5
defer func() {
t = t + 5
}()
return t
}

func h() (r int) {
defer func(r int) {
r = r + 5
fmt.Println®

return 1
}

func main() {
fmt.Println(f())
fmt.Println(g())
fmt.Println(h())
return
}
trap.1.output 折叠源码
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
43
44
45
46
output:
1
5
5
1

函数返回的过程是这样的:先给返回值赋值,然后调用defer表达式,最后才是返回到调用函数中。

A “return” statement in a function F terminates the execution of F, and optionally provides one or more result values. Any functions deferred by F are executed before F returns to its caller.
A “return” statement that specifies results sets the result parameters before any deferred functions are executed.

refer : https://golang.org/ref/spec#defer_statements

修改tarp1:
func f() (result int) {
result = 0 //return语句不是一条原子调用,return xxx其实是赋值+ret指令
func() { //defer被插入到return之前执行,也就是赋返回值和ret指令之间
result++
}()
return result
}

func g() (r int) {
t := 5
r = t //赋值指令
func() { //defer被插入到赋值与返回之间执行,这个例子中返回值r没被修改过
t = t + 5
}
return r
}

func h() (r int) {
arg := 0
r = 1 //给返回值赋值
func(r int) { //这里改的r是传值传进去的r,不会改变要返回的那个r值
r = r + 5
}(arg)
return r
}

tips : 不得已,尽量不要使用defer修改函数返回值,非常容易引起歧义。

4.2 循环中的trap

defer row.Close() 在循环中的延迟函数会在函数结束过后运行,而不是每次 for 循环结束之后。这些延迟函数会不停地堆积到延迟调用栈中,最终可能会导致一些不可预知的问题。

     tips : 注意需要立即释放的内容不要使用defer

4.3 执行块中使用defer

trap.3 折叠源码
1
2
3
4
5
6
7
8
9
10
11
12
13
package main

func main() {
{
defer func() {
fmt.Println(“block: defer runs”)
}()

fmt.Println("block: ends")

}

fmt.Println(“main: ends”)
}
trap.3.output 折叠源码
1
2
3
4
5
6
7
8
output:
block: ends
main: ends
block: defer runs

tips : defer在所属的函数执行结束时才执行,和代码块没有关系

4.4 调用结构体方法

trap.4 折叠源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
type Car struct {
model string
}

func (c Car) PrintModel() {
fmt.Println(c.model)
}

func main() {
c := Car{model: “DeLorean DMC-12”}

defer c.PrintModel()

c.model = “Chevrolet Impala”
}
trap.3.output 折叠源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
没有使用指针作为接收者
output:
DeLorean DMC-12

使用指针对象作为接收者
func (c *Car) PrintModel() {
fmt.Println(c.model)
}
output:
Chevrolet Impala

tips : 注意传值还是传指针
五.defer 原理
1.从汇编看原理

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

import “fmt”

func main() {
test()
}

func test() int {
defer dfun()
defer dfun2()
return 1
}

func dfun() {
fmt.Printf(“i am defer”)
}

func dfun2() (string, map[int]int){
fmt.Printf(“i am defer”)

return "", nil

}

refer : https://www.jianshu.com/p/7d2c6f852aea
go build -gcflags -S code.go生成对应汇编

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
29
30
31
32
33
34
0x0038 00056 (/home/work/learn/learn_go/src/learn_defer/code.go:10) PCDATA $2, $0
48 0x0038 00056 (/home/work/learn/learn_go/src/learn_defer/code.go:10) MOVQ AX, 8(SP)
49 0x003d 00061 (/home/work/learn/learn_go/src/learn_defer/code.go:10) CALL runtime.deferproc(SB)
50 0x0042 00066 (/home/work/learn/learn_go/src/learn_defer/code.go:10) TESTL AX, AX
51 0x0044 00068 (/home/work/learn/learn_go/src/learn_defer/code.go:10) JNE 139
52 0x0046 00070 (/home/work/learn/learn_go/src/learn_defer/code.go:11) MOVL $24, (SP)
53 0x004d 00077 (/home/work/learn/learn_go/src/learn_defer/code.go:11) PCDATA $2, $1
54 0x004d 00077 (/home/work/learn/learn_go/src/learn_defer/code.go:11) LEAQ “”.dfun2·f(SB), AX
55 0x0054 00084 (/home/work/learn/learn_go/src/learn_defer/code.go:11) PCDATA $2, $0
56 0x0054 00084 (/home/work/learn/learn_go/src/learn_defer/code.go:11) MOVQ AX, 8(SP)
57 0x0059 00089 (/home/work/learn/learn_go/src/learn_defer/code.go:11) CALL runtime.deferproc(SB)
58 0x005e 00094 (/home/work/learn/learn_go/src/learn_defer/code.go:11) TESTL AX, AX
59 0x0060 00096 (/home/work/learn/learn_go/src/learn_defer/code.go:11) JNE 123
60 0x0062 00098 (/home/work/learn/learn_go/src/learn_defer/code.go:12) MOVQ $1, “”.~r0+56(SP)
61 0x006b 00107 (/home/work/learn/learn_go/src/learn_defer/code.go:12) XCHGL AX, AX
62 0x006c 00108 (/home/work/learn/learn_go/src/learn_defer/code.go:12) CALL runtime.deferreturn(SB)
63 0x0071 00113 (/home/work/learn/learn_go/src/learn_defer/code.go:12) MOVQ 40(SP), BP
64 0x0076 00118 (/home/work/learn/learn_go/src/learn_defer/code.go:12) ADDQ $48, SP
65 0x007a 00122 (/home/work/learn/learn_go/src/learn_defer/code.go:12) RET
66 0x007b 00123 (/home/work/learn/learn_go/src/learn_defer/code.go:11) XCHGL AX, AX
67 0x007c 00124 (/home/work/learn/learn_go/src/learn_defer/code.go:11) CALL runtime.deferreturn(SB)
68 0x0081 00129 (/home/work/learn/learn_go/src/learn_defer/code.go:11) MOVQ 40(SP), BP
69 0x0086 00134 (/home/work/learn/learn_go/src/learn_defer/code.go:11) ADDQ $48, SP
70 0x008a 00138 (/home/work/learn/learn_go/src/learn_defer/code.go:11) RET
71 0x008b 00139 (/home/work/learn/learn_go/src/learn_defer/code.go:10) XCHGL AX, AX
72 0x008c 00140 (/home/work/learn/learn_go/src/learn_defer/code.go:10) CALL runtime.deferreturn(SB)
73 0x0091 00145 (/home/work/learn/learn_go/src/learn_defer/code.go:10) MOVQ 40(SP), BP
74 0x0096 00150 (/home/work/learn/learn_go/src/learn_defer/code.go:10) ADDQ $48, SP
75 0x009a 00154 (/home/work/learn/learn_go/src/learn_defer/code.go:10) RET
76 0x009b 00155 (/home/work/learn/learn_go/src/learn_defer/code.go:10) NOP
77 0x009b 00155 (/home/work/learn/learn_go/src/learn_defer/code.go:9) PCDATA $0, $-1
78 0x009b 00155 (/home/work/learn/learn_go/src/learn_defer/code.go:9) PCDATA $2, $-1
79 0x009b 00155 (/home/work/learn/learn_go/src/learn_defer/code.go:9) CALL runtime.morestack_noctxt(SB)
80 0x00a0 00160 (/home/work/learn/learn_go/src/learn_defer/code.go:9) JMP 0
2.deferreturn、deferproc

deferproc先通过newdefer函数来生成一个Defer对象,封装调用函数的一些信息,然后把它加到goroutine的g对象的defer链表中。

相关资料:

https://golang.org/src/runtime/panic.go?s=2613:2651#L82 deferproc

deferreturnhttps://golang.org/src/runtime/panic.go?s=9427:9457#L326 deferreturn

http://3ms.huawei.com/km/blogs/details/5012483 defer内部详解

六.性能消耗
并不是一定要用defer,defer带来了便利,也带来了消耗。

performance 折叠源码
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
package main

import (
“sync”
“testing”
)

var (
lock = new(sync.Mutex)
)

func lockTest() {
lock.Lock()
lock.Unlock()
}
func lockDeferTest() {
lock.Lock()
defer lock.Unlock()
}
func BenchmarkTest(b *testing.B) {
for i := 0; i < b.N; i++ {
lockTest()
}
}
func BenchmarkTestDefer(b testing.B) {
for i := 0; i < b.N; i++ {
lockDeferTest()
}
}
go test -v -test.bench=".
"

performance.output 折叠源码
1
2
3
4
5
6
7
8
output:

goos: linux
goarch: amd64
BenchmarkTest-4 50000000 25.0 ns/op
BenchmarkTestDefer-4 20000000 73.8 ns/op
PASS
ok _/home/work/learn/learn_go/src/learn_defer/performance 2.861s

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值