return语句

  1. package main
  2. func f() int {
  3. i := 5
  4. defer func() {
  5. i++
  6. }()
  7. return i
  8. }
  9. func f1() (result int) {
  10. defer func() {
  11. result++
  12. }()
  13. return 0
  14. }
  15. func f2() (r int) {
  16. t := 5
  17. defer func() {
  18. t = t + 5
  19. }()
  20. return t
  21. }
  22. func f3() (r int) {
  23. defer func(r int) {
  24. r = r + 5
  25. }(r)
  26. return 1
  27. }
  28. func main() {
  29. println(f())
  30. println(f1())
  31. println(f2())
  32. println(f3())
  33. }
  1. go build -gcflags '-l' -o foo foo.go
  2. $ go tool objdump -s "main\.foo" foo
  3. TEXT main.foo(SB) /Users/kltao/code/go/src/example/foo.go
  4. bar.go:6 0x104ea70 48c744240801000000 MOVQ $0x1, 0x8(SP)
  5. bar.go:6 0x104ea79 48c744241002000000 MOVQ $0x2, 0x10(SP)
  6. bar.go:6 0x104ea82 c3 RET

其实return也不是原子操作,而是被拆成两步:

  1. rval = xxx
  2. defer_func
  3. ret

如果在return中返回的变量是显示声明的,比如说func foo() (ret int) {},rval也就是ret。所以返回的函数执行效果如下:

  1. //f
  2. rval = i
  3. i ++
  4. ret
  5. //f1
  6. result = 0
  7. defer // result ++
  8. return
  9. //f2
  10. r = t
  11. defer // t = t + 5
  12. return

闭包

  1. Go 语言中的闭包就是在函数内引用函数体之外的数据,这样就会产生一种结果,虽然数据定义是在函数外,但是在函数内部操作数据也会对数据产生影响
  2. 匿名函数对 i 的调用就是闭包引用,i++ 会影响外面定义的 i 的值。而 bar() 中的匿名函数是变量拷贝,i++ 并不会修改外部 i 值 ```go func foo() { i := 1 go func() {
    1. i ++
    }() time.Sleep(xxx) println(i) } // i = 2

func bar() { i := 1 go func(i int) { i ++ }(i) time.Sleep(xxx) println(i) } // i = 1 ```

defer的使用

我们在日常的生活中日常的项目,defer可以让我们不需要显示地关闭文件句柄,网络连接等。特别是一些当函数出现异常或者错误,提前返回了。这种方式是非常高效的。

defer底层的实现了一个栈,先进后出。每次使用defer关键字,就创建一个defer对象,以链表的形式关联到当前的goroutine中。有新的defer对象就插入表头中,执行时从表头取,这就实现了一个栈。

可见defer的实现成本是比较高的,我们有空可以写一个benchmark测试一下。因此,当我们知道应该在哪里释放的资源的时候,应该显示地释放资源。

defer + panic

使用 defer 的最合适的场景我觉得应该是和 recover 结合使用,也就是说在你不知道的程序何时可能会 panic 的时候,才引入 defer + recover

参考

深入理解 Go 语言 defer