Go defer关键字的实现原理?

定义

defer 能够让我们推迟执行某些函数调用,推迟到当前函数返回前才实际执行。defer与panic和recover结合,形成了Go语言风格的异常与捕获机制。

使用场景

defer 语句经常被用于处理成对的操作,如文件句柄关闭、连接关闭、释放锁

优点:

方便开发者使用

缺点:

有性能损耗

实现原理

Go1.14中编译器会将defer函数直接插入到函数的尾部,无需链表和栈上参数拷贝,性能大幅提升。把defer函数在当前函数内展开并直接调用,这种方式被称为open coded defer

源代码:

  1. func A(i int) {
  2. defer A1(i, 2*i)
  3. if(i > 1) {
  4. defer A2("Hello", "eggo")
  5. }
  6. // code to do something
  7. return
  8. }
  9. func A1(a,b int) {
  10. //......
  11. }
  12. func A2(m,n string) {
  13. //......
  14. }

编译后(伪代码):

  1. func A(i int) {
  2. // code to do something
  3. if(i > 1){
  4. A2("Hello", "eggo")
  5. }
  6. A1(i, 2*i)
  7. return
  8. }

代码示例

  1. 函数退出前,按照先进后出的顺序,执行defer函数
  1. package main
  2. import "fmt"
  3. // defer:延迟函数执行,先进后出
  4. func main() {
  5. defer fmt.Println("defer1")
  6. defer fmt.Println("defer2")
  7. defer fmt.Println("defer3")
  8. defer fmt.Println("defer4")
  9. fmt.Println("11111")
  10. }
  11. // 11111
  12. // defer4
  13. // defer3
  14. // defer2
  15. // defer1
  1. panic后的defer函数不会被执行(遇到panic,如果没有捕获错误,函数会立刻终止)
  1. package main
  2. import "fmt"
  3. // panic后的defer函数不会被执行
  4. func main() {
  5. defer fmt.Println("panic before")
  6. panic("发生panic")
  7. defer func() {
  8. fmt.Println("panic after")
  9. }()
  10. }
  11. // panic before
  12. // panic: 发生panic
  1. panic没有被recover时,抛出的panic到当前goroutine最上层函数时,最上层程序直接异常终止
  1. package main
  2. import "fmt"
  3. func F() {
  4. defer func() {
  5. fmt.Println("b")
  6. }()
  7. panic("a")
  8. }
  9. // 子函数抛出的panic没有recover时,上层函数时,程序直接异常终止
  10. func main() {
  11. defer func() {
  12. fmt.Println("c")
  13. }()
  14. F()
  15. fmt.Println("继续执行")
  16. }
  17. // b
  18. // c
  19. // panic: a
  1. panic有被recover时,当前goroutine最上层函数正常执行
  1. package main
  2. import "fmt"
  3. func F() {
  4. defer func() {
  5. if err := recover(); err != nil {
  6. fmt.Println("捕获异常:", err)
  7. }
  8. fmt.Println("b")
  9. }()
  10. panic("a")
  11. }
  12. func main() {
  13. defer func() {
  14. fmt.Println("c")
  15. }()
  16. F()
  17. fmt.Println("继续执行")
  18. }
  19. // 捕获异常: a
  20. // b
  21. // 继续执行
  22. // c