这是我参与「第三届青训营 -后端场」笔记创作活动的的第1篇笔记
概述
go语言的 defer 函数很特别,它在声明时不会立刻去执行,而是在函数 return 后去执行的(容易联想到Java的finally关键字,但是非常不一样)
它的主要应用场景有异常处理、记录日志、清理数据、释放资源 等等。
首先大家可以判断一下一下代码的执行结果,看是否正确
func calc(index string, a, b int) int {
ret := a + b
fmt.Println(index, a, b, ret)
return ret
}
func main() {
x := 1
y := 2
defer calc("A", x, calc("B", x, y))
x = 3
defer calc("C", x, calc("D", x, y))
y = 4
}
实际的执行结果如下,有点想不明白
B 1 2 3
D 3 2 5
C 3 5 8
A 1 3 4
如果不明白为什么会输出这样的结果,可以看一下接下来的介绍
defer的执行顺序
func main() {
defer fmt.Println("1")
defer fmt.Println("2")
defer fmt.Println("3")
fmt.Println("main")
}
输出:
main
3
2
1
结论:defer 函数定义的顺序 与 实际执的行顺序是相反的,也就是最先声明的最后才执行。
闭包的特性
func main() {
var a = 1
var b = 2
defer fmt.Println(a + b)
a = 2
fmt.Println("main")
}
输出:
main
3
稍微修改一下,再看看:
func main() {
var a = 1
var b = 2
defer func() {
fmt.Println(a + b)
}()
a = 2
fmt.Println("main")
}
输出:
main
4
结论:闭包获取变量相当于引用传递,而非值传递。
稍微再修改一下,再看看:
func main() {
var a = 1
var b = 2
defer func(a int, b int) {
fmt.Println(a + b)
}(a, b)
a = 2
fmt.Println("main")
}
输出:
main
3
结论:传参是值复制。
还可以理解为:defer 调用的函数,参数的值在 defer 定义时就确定了,看下代码
defer fmt.Println(a + b),在这时,参数的值已经确定了。
而 defer 函数内部所使用的变量的值需要在这个函数运行时才确定,看下代码
defer func() { fmt.Println(a + b) }(),a 和 b 的值在函数运行时,才能确定。
Return测试
测试一
func t1() int {
a := 1
defer func() {
a++
}()
return a
}
输出:1
测试二
func t2() (a int) {
defer func() {
a++
}()
return 1
}
输出:2
测试三
func t3() (b int) {
a := 1
defer func() {
a++
}()
return 1
}
输出:1
测试四
func t4() (a int) {
defer func(a int) {
a++
}(a)
return 1
}
输出:1
结论:return 不是原子操作。
os.Exit执行的情况
func main() {
defer fmt.Println("1")
fmt.Println("main")
os.Exit(0)
}
输出:main
结论:当os.Exit()方法退出程序时,defer不会被执行。
不同协程情况下
func main() {
GoA()
time.Sleep(1 * time.Second)
fmt.Println("main")
}
func GoA() {
defer (func(){
if err := recover(); err != nil {
fmt.Println("panic:" + fmt.Sprintf("%s", err))
}
})()
go GoB()
}
func GoB() {
panic("error")
}
GoB() panic 捕获不到。
结论:defer 只对当前协程有效。
defer特性总结
defer可以将指定的语句延迟执行,用于实现资源的回收。
规则一:在函数退出时执行
需要注意,defer后的语句是在函数退出时执行。
package main
import "fmt"
func main() {
if true {
defer fmt.Println("world")
}
fmt.Printf("hello ")
}
规则二:当defer被声明时,其参数就会被实时解析
下面的代码最终输出0,因为n作为参数传入的时候就已经被解析了。
package main
import "fmt"
func main() {
n := 0
defer fmt.Println(n)
n++
}
规则三:defer执行顺序为先进后出
多条defer语句就会进行压栈,先声明的先执行。 下面的代码输出3 2 1 0。
package main
import "fmt"
func main() {
for i := 0; i < 4; i++ {
defer fmt.Println(i)
}
}