Go进阶:踩坑defer的各种方式

72 阅读3分钟

defer的使用场景

defer关键字主要是用于延迟调用

  • 可以放在函数的任何位置
  • defer函数的传入参数在定义的时候就确定
  • 函数返回之前执行
  • 可以同时设置多个defer函数,多个defer函数执行类似于栈的方式,先进后出
  • 常用网络连接、数据库连接的释放
  • 和recover一起处理panic

资源的释放

通过defer延迟调用机制,可以简洁优雅处理资源回收问题,避免在复杂代码逻辑下,遗漏资源回收的相关问题

  • 关闭db连接池
// db connection pool closer
defer func(db *gorm.DB) {
   pool, _ := db.DB()
   _ = pool.Close()
}(db)

和revocer一起处理panic

defer另外一个常用的地方就是处理程序panic的时候,当Go语言用panic来抛出异常,用recover来捕获异常,所以当我们程序出现异常的时候,需要知道发生了什么异常,可以用defer revover来捕获异常

func main() {
   defer func() {
      if r := recover(); r != nil {
         fmt.Println(r)
      }
   }()
   a := 1
   b := 0
   fmt.Println(a / b)
}

打印结果

runtime error: integer divide by zero

return 和 defer 的爱恨情仇

函数的return并非一个原子操作

  • 分别三步:
    • 设置返回值
    • 执行defer语句
    • 将结果返回

第一段感情

func Run()  {
   var num = 1
   defer fmt.Println("num:",num)
   num++
}
func main() {
   Run()
}
num: 1
  • defer函数的传入参数在定义的时候就确定:defer fmt.Println("num:",num)在定义的时候参数num就已经确定了,num就确定为1,后面的改变不会影响此时的打印

第二段感情

func getArr(arr *[2]int) {
   for i := range arr {
      fmt.Println(arr[i])
   }
}
func Run() {
   var arr = [2]int{1, 2}
   defer getArr(&arr)
   arr[0] = 3
}
func main() {
   Run()
}
  • 有同学就疑惑了,这里不是defer在定义的时候参数不会变化吗?
    • 确实没有变化,这里传的是地址,地址没有变化,但是地址里面的内容改变了

第三段感情

func Run() (res int){
   num := 1
   defer func() {
      res++
   }()
   return num
}
func main() {
   Run()
   fmt.Println(res)
}
  • 第一步设置返回值为1,然后执行defer语句将res+1,最终将res返回,所以打印出来的值为2

第三段感情

func Run() int {
   var num int
   defer func() {
      num++
   }()
   return 1
}
func main() {
   res := Run()
   fmt.Println(res)
}
  • 与上面的区别是匿名的,但是同样是return的逻辑,首先设置返回值res = 1,然后执行defer语句 num++,第三步将res返回,所以打印出来的值为1

第四段感情

func Run() int {
   var num = 1
   defer func() {
      num++
   }()
   return num
}
func main() {
   res := Run()
   fmt.Println(res)
}
  • 首先设置res等于num,所以res的值为1,第二步将执行defer将num+1,此时num为2,但是res为1,第三步将res返回,所以最终结果是1

第五段感情

func Run() (res int) {
   var num = 1
   defer func() {
      num++
   }()
   return num
}
func main() {
   res := Run()
   fmt.Println(res)
}
  • 分析结果:首先设置res = num,然后执行num++,最终返回res = 1

总结

  • defer 定义的延迟函数的参数在 defer 语句的时候就已经确定下来
  • return 不是原子操作
    • 1.设置返回值
    • 2.执行defer语句
    • 3.将结果返回