go defer运行机制
概念
Go 语言的 defer
会在当前函数返回前执行传入的函数,它会经常被用于关闭文件描述符、关闭数据库连接以及解锁资源。
现象
-
defer 先进后出
package main import "fmt" func main() { for i := 0; i < 5; i++ { defer fmt.Println(i) } }
go run main.go 4 3 2 1 0
-
defer
传入的函数不是在退出代码块的作用域时执行的,它只会在当前函数和方法返回之前被调用package main import "fmt" func main() { func() { defer fmt.Println("匿名函数结束") fmt.Println("匿名函数。。。") }() { defer fmt.Println("代码块结束") fmt.Println("代码块。。。") } fmt.Println("正常") }
go run main.go 匿名函数。。。 匿名函数结束 代码块。。。 正常 代码块结束
-
参数的计算
对于 defer2,因为 i 是以参数的形式传递进去的,所以 defer 会立刻拷贝 i 参数,此时的 i=5
对于 defer1,因为函数执行结束时 i=7,defer 中使用的是外部的 i,所以 defer 中也是 7
package main import "fmt" func main() { defer1() defer2() } func defer1() { i := 5 defer func() { fmt.Println("defer: ", i) }() i = 7 fmt.Println("正常:", i) } func defer2() { i := 5 defer func(i int) { fmt.Println("defer: ", i) }(i) i = 7 fmt.Println("正常:", i) }
go run main.go 正常: 7 defer: 7 正常: 7 defer: 5
同样的道理,结构体也是如此,当为引用时复制的只是结构体的地址,指向的还是同一块内存,当结构体改变时 defer 中打印的也是改变之后的值
package main import "fmt" func main() { defer3() defer4() defer5() } type Student struct { Name string } func defer3() { s := Student{"liu"} defer func() { fmt.Println("defer: ", s) }() s.Name = "kang" fmt.Println("正常:", s) } func defer4() { s := Student{"liu"} defer func(s Student) { fmt.Println("defer: ", s) }(s) s.Name = "kang" fmt.Println("正常:", s) } func defer5() { s := &Student{"liu"} defer func(s *Student) { fmt.Println("defer: ", s) }(s) s.Name = "kang" fmt.Println("正常:", s) }
go run main.go 正常: {kang} defer: {kang} 正常: {kang} defer: {liu} 正常: &{kang} defer: &{kang}
原理
- 后调用的 defer 函数会先执行:
- 后调用的
defer
函数会被追加到 Goroutine_defer
链表的最前面; - 运行
runtime._defer
时是从前到后依次执行;
- 后调用的
- 函数的参数会被预先计算;
- 调用
runtime.deferproc
函数创建新的延迟调用时就会立刻拷贝函数的参数,函数的参数不会等到真正执行时计算;
- 调用