Defer Panic and Recover
in Go with 0 comment

Defer Panic and Recover

in Go with 0 comment

Defer

延时操作,在函数return时执行的操作

一般用来释放锁资源,关闭文件等清理操作。

在多处返回时,可以简化清理流程。

捕获异常

使用场景

mu.Lock()
defer mu.Unlock()
f,err := os.Open(filename)
if err != nil {
    panic(err)
}

if f != nil {
    defer f.Close()
}

defer触发时机

A "defer" statement invokes a function whose execution is deferred to the moment the surrounding function returns, either because the surrounding function executed a return statement, reached the end of its function body, or because the corresponding goroutine is panicking.

Go官方文档中对defer的执行时机做了阐述,分别是。

注意事项

后进先出

defer 的执行顺序是后进先出

func main() {
	defer fmt.Println(1)
	defer fmt.Println(2)
	defer fmt.Println(3)
}
// Output
// 3
// 2
// 1

出现 panic 语句的时候,会先按照 defer 的后进先出的顺序执 行,最后才会执行panic

func main() {
	defer fmt.Println(1)
	defer fmt.Println(2)
	defer fmt.Println(3)
	panic("my panic")
}

// Output
// 3
// 2
// 1
// panic: my panic
func defer_call() {
   defer func() { fmt.Println("打印前") }()
   defer func() { fmt.Println("打印中") }()
   defer func() { fmt.Println("打印后") }()
   panic("触发异常")
}

func main() {
   fmt.Println("Start")
   defer fmt.Println(1)
   defer fmt.Println(2)
   defer fmt.Println(3)
   defer_call()
   panic("my panic")
   fmt.Println("Done")
}

// Output
// Start
// 打印后
// 打印中
// 打印前
// 3
// 2
// 1
// panic: 触发异常

defer的嵌套

defer在当前函数return时执行

踩坑点

使用 defer 最容易采坑的地方是和带命名返回参数的函数一起使用时。

defer 语句定义时,对外部变量的引用是有两种方式的,分别是作为函数参数和作为闭包引用。作为函数参数,则在 defer 定义时就把值传递给 defer,并被缓存起来;作为闭包引用的话,则会在 defer 函数真正调用时根据整个上下文确定当前的值。

return xxx

等价于

返回值 = xxx
调用 defer 函数
空的 return
func increaseA() int {
    var i int
    defer func() {
        i++
    }()
    return i
}
// 0
// annoy = i
// i++
// return
func increaseB() (r int) {
    defer func() {
        r++
    }()
    return r
}
// 1
func f1() (r int) {
	defer func() {
		r++
	}()
	return 0
}
// 1
func f2() (r int) {
   t := 5
   defer func() {
      t = t + 5
   }()
   return t
}
// 5
func f3() (r int) {
   defer func() {
      r = r + 5
   }()
   return 1
}
// 6
func f4() (r int) {
   defer func(r int) {
      r = r + 5
   }(r)
   return 1
}
// 1
func main() {
    var whatever [3]struct{}
    
    for i := range whatever {
        defer func() { 
            fmt.Println(i) 
        }()
    }
}
// 2
// 2
// 2
type number int

func (n number) print()   { fmt.Println(n) }
func (n *number) pprint() { fmt.Println(*n) }

func main() {
    var n number

    defer n.print()
    defer n.pprint()
    defer func() { n.print() }()
    defer func() { n.pprint() }()

    n = 3
}
// 3 闭包,引用外部函数的n, 最终结果是3
// 3 同第四个
// 3 n是引用,最终求值是3
// 0 对n直接求值,开始的时候n=0, 所以最后是0
func main() {
   defer fmt.Println("defer main")
   var user = os.Getenv("USER_")

   go func() {
      defer func() {
         fmt.Println("defer caller")
         if err := recover(); err != nil {
            fmt.Println("recover success. err: ", err)
         }
      }()

      func() {
         defer func() {
            fmt.Println("defer here")
         }()

         if user == "" {
            panic("should set user env.")
         }

         // 此处不会执行
         fmt.Println("after panic")
      }()
      fmt.Println("Goroutine Done")
   }()

   time.Sleep(time.Second)
   fmt.Println("end of main function")
}

// Output
// defer here
// defer caller
// recover success. err:  should set user env.
// -------
// end of main function
// defer main

Panic

嵌套panic

func main() {
	defer fmt.Println("in main")
	defer func() {
		defer func() {
			panic("panic again and again")
		}()
		panic("panic again")
	}()

	panic("panic once")
}

$ go run main.go
in main
panic: panic once
	panic: panic again
	panic: panic again and again

goroutine 1 [running]:
...
exit status 2

编译器会将关键字 panic 转换成 runtime.gopanic

编译器会将关键字 recover 转换成 runtime.gorecover

package main

import "fmt"

func test2() {
	defer func() {
		fmt.Println("defer Test02")
		if r := recover(); r != nil {
			fmt.Println("recover", r)
			panic("test2 defer panic")
		}
	}()
	panic("test2 panic")
	fmt.Println("test2 Done")
}

func test1() {
	defer func() {
		fmt.Println("defer Test01")
		if r := recover(); r != nil {
			fmt.Println("recover", r)
		}
	}()
	test2()
	fmt.Println("test1 Done")
}

func main() {
	defer func() { fmt.Println("defer main") }()
	test1()
	fmt.Println("main Done")
}
// OutPut:
// defer Test02
// recover test2 panic
// defer Test01
// recover test2 defer panic
// main Done
// defer main

实现详解

编译器会将 defer 关键字都转换成 runtime.deferproc 函数,函数负责创建新的延迟调用

它还会为所有调用 defer 的函数末尾插入 runtime.deferreturn 的函数调用,函数负责在函数调用结束时执行所有需要的的延迟调用

runtime的几种 panic

优化点

栈上分配

开放编码

defer

deferreturn

https://studygolang.com/articles/5932

https://juejin.im/post/5b9b4acde51d450e5071d51f

5 年 Gopher 都不知道的 defer 细节,你别再掉进坑里!

https://segmentfault.com/a/1190000018169295#articleHeader4

https://qcrao.com/2020/03/23/how-to-traverse-defer-links/