Golang defer的运行时机和遇到的坑

马谦的博客 · · 829 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

一、defer概述

defergolang 中独有的流程控制语句,用于延迟指定语句的运行时机,只能运行于函数的内部,且当他所属函数运行完之后它才会被调用。例如:

1
2
3
4
func deferTest(){
defer fmt.Println("HelloDefer")
fmt.Println("HelloWorld")
}

它会先打印出HelloWorld ,然后再打印出HelloDefer

一个函数中如果有多个defer ,运行顺序和函数中的调用顺序相反,因为它们都是被写在了栈中:

1
2
3
4
5
func deferTest(){
defer fmt.Println("HelloDefer1")
defer fmt.Println("HelloDefer2")
fmt.Println("HelloWorld")
}

运行结果:

1
2
3
fmt.Println("HelloDefer2")
fmt.Println("HelloDefer1")
fmt.Println("HelloWorld")

二、defer和return

在包含有return 语句的函数中,defer 的运行顺序位于return 之后,但是defer 所运行的代码片段会生效:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func main(){
fmt.Println(deferReturn)
}
func deferReturn() int{
i := 1
defer func(){
fmt.Println("Defer")
i += 1
}()
return func()int{
fmt.Println("Return")
return i
}()
}

运行结果:

1
2
3
Return
Defer
1

这里很明显就能看到defer 是在return 之后运行的!但是有一个问题是defer 里执行了语句i +=1 ,按照这个逻辑的话返回的i 值应该是2 而不是1 。这个问题是由于return 的运行机制导致的:return 在返回一个对象时,如果返回类型不是指针或者引用类型,那么return 返回的就不会是这个对象本身,而是这个对象的副本。

我们可以验证这一个观点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func main(){
...
fmt.Println("main: ", x, &x)
}
func deferReturn() int{
...
defer ...{
fmt.Println("Defer: ", i, &i)
...
}()
return ...{
fmt.Println("Return: ", i, &i)
...
}()
}

程序的输出为:

1
2
3
Return:     1 0xc042008238
Defer: 1 0xc042008238
main: 1 0xc042008230 //main函数中的i的地址和deferReturn()中的i的地址是不一样的

如果把函数的返回值改成指针类型,这时候的main函数中的返回值就会和函数体内的一致:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func main(){
x := deferReturn()
fmt.Println("main: ", x, *x)
}
func deferReturn()*int{
i := 1
p := &i
defer func() {
*p += 1
fmt.Println("defer: ", p, *p)
}()
return func() *int{
fmt.Println("Return: ", p, *p)
return p
}()
}

结果:

1
2
3
Return:     0xc0420361d0 1
defer: 0xc0420361d0 2
main: 0xc0420361d0 2

三、defer和panic

panic 会在defer 运行完之后才把恐慌扩散到其他函数:

1
2
3
4
func deferPanic(){
defer fmt.Println("HelloDefer")
panic("Hey, I"m panic")
}

结果:

1
2
3
4
5
6
7
HelloDefer  //会先输出defer部分的代码
panic: Hey, I"m panic
goroutine 1 [running]:
main.deferTest()
E:/code/golang/src/test_src/defer/main.go:12 +0xfc
main.main()
E:/code/golang/src/test_src/defer/main.go:6 +0x27

四、defer和for循环

不要在defer 内使用外部变量,可能会造成一些意想不到的错误:

1
2
3
4
5
6
7
func deferTest(){
for i := 0; i < 5; i ++{
defer func(){
fmt.Printf("%d", i)
}()
}
}

它的输出的结果是55555 ,原理很简单,因为defer会在for循环运行完后才会调用,for循环运行完时i的值为5,所以打印的i值会是55555

但是如果循环内的延时函数有参数传入,参数就会在当前defer 语句执行的时候求出:

1
2
3
4
5
6
7
func deferTest(){
for i := 0; i < 5; i ++{
defer func(i int){
fmt.Printf("%d", i)
}(i)
}
}

此时就会打印出43210 而不是55555 了。


有疑问加站长微信联系(非本文作者)

本文来自:马谦的博客

感谢作者:马谦的博客

查看原文:Golang defer的运行时机和遇到的坑

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:701969077

829 次点击  
加入收藏 微博
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传