return语句
package mainfunc f() int {i := 5defer func() {i++}()return i}func f1() (result int) {defer func() {result++}()return 0}func f2() (r int) {t := 5defer func() {t = t + 5}()return t}func f3() (r int) {defer func(r int) {r = r + 5}(r)return 1}func main() {println(f())println(f1())println(f2())println(f3())}
go build -gcflags '-l' -o foo foo.go$ go tool objdump -s "main\.foo" fooTEXT main.foo(SB) /Users/kltao/code/go/src/example/foo.gobar.go:6 0x104ea70 48c744240801000000 MOVQ $0x1, 0x8(SP)bar.go:6 0x104ea79 48c744241002000000 MOVQ $0x2, 0x10(SP)bar.go:6 0x104ea82 c3 RET
其实return也不是原子操作,而是被拆成两步:
rval = xxxdefer_funcret
如果在return中返回的变量是显示声明的,比如说func foo() (ret int) {},rval也就是ret。所以返回的函数执行效果如下:
//frval = ii ++ret//f1result = 0defer // result ++return//f2r = tdefer // t = t + 5return
闭包
- Go 语言中的闭包就是在函数内引用函数体之外的数据,这样就会产生一种结果,虽然数据定义是在函数外,但是在函数内部操作数据也会对数据产生影响
- 匿名函数对 i 的调用就是闭包引用,i++ 会影响外面定义的 i 的值。而 bar() 中的匿名函数是变量拷贝,i++ 并不会修改外部 i 值
```go
func foo() {
i := 1
go func() {
}() time.Sleep(xxx) println(i) } // i = 2i ++
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
