分享
  1. 首页
  2. 文章

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

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

一、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群:692541889

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

用户登录

没有账号?注册
(追記) (追記ここまで)

今日阅读排行

    加载中
(追記) (追記ここまで)

一周阅读排行

    加载中

关注我

  • 扫码关注领全套学习资料 关注微信公众号
  • 加入 QQ 群:
    • 192706294(已满)
    • 731990104(已满)
    • 798786647(已满)
    • 729884609(已满)
    • 977810755(已满)
    • 815126783(已满)
    • 812540095(已满)
    • 1006366459(已满)
    • 692541889

  • 关注微信公众号
  • 加入微信群:liuxiaoyan-s,备注入群
  • 也欢迎加入知识星球 Go粉丝们(免费)

给该专栏投稿 写篇新文章

每篇文章有总共有 5 次投稿机会

收入到我管理的专栏 新建专栏