Go从入门到放弃12--函数2

46 阅读3分钟

defer

在一些在函数中会申请一些资源并在函数退出前释放或关闭这些资源,比如文件描述符和互斥锁等。函数的实现需要确保这些资源在函数退出时被及时正确地释放,无论函数的执行流是按预期顺利进行还是出现错误提前退出。为此,开发人员需对函数中的错误处理尤为关注,在错误处理时不能遗漏对资源的释放,尤其是有多个资源需要释放的时候,这大大增加了开发人员的心智负担 。此外,当待释放的资源个数较多时,代码逻辑将变得十分复杂,程序可读性、健壮性也随之下降。但即便如此,如果函数实现中的某段代码逻辑抛出panic,传统的错误处理机制依然没有办法捕获它并尝试从panic中恢复。

解决上面提到的这些问题正是Go语言引入defer的初衷。

defer运行机制

  • 在Go中,只有在函数和方法内部才能使用defer
  • defer关键字后面只能接函数或方法,这些函数被称为deferred函数。defer将它们注册到其所在goroutine用于存放deferred函数的栈数据结构中,这些deferred函数将在执行defer的函数退出前被按后进先出(LIFO)的顺序调度执行

得到App_2022-07-18_23-11-53.png

defer的常见用途

  • 关闭文件句柄
  • 锁资源释放
  • 数据库连接释放

释放资源的defer应该直接跟在请求资源的语句后

defer注意事项

明确哪些函数可以作为deferred函

对于自定义的函数或方法,defer可以给予无条件的支持,但是对于有返回值的自定义函数或方法,返回值会在deferred函数被调度执行的时候被自动丢弃。

append、cap、len、make、new等内置函数是不可以直接作为deferred函数的,而close、copy、delete、print、recover等可以。

注意 defer 关键字后面表达式的求值时机

defer关键字后面的表达式是在将deferred函数注册到deferred函数栈的时候进行求值的。

func main() {
   a := 1
   defer func(b int) {
      fmt.Println("defer b", b) //defer函数打印出的b值是2
   }(a + 1)
   a = 99
}

panic和recover

Go的类型系统会在编译时捕获很多错误,但有些错误只能在运行时检查,如数组访问越界、空指针引用等。这些运行时错误会引起painc异常。无论在哪个 Goroutine 中发生未被恢复的 panic,整个程序都将崩溃退出

有时候我们并不希望程序异常退出,为了让程序在panic时仍然能够执行后续的流程,Go语言提供了内置的recover函数用于异常恢复。recover函数一般与defer函数结合使用才有意义,其返回值是panic中传递的参数。由于panic会调用defer函数,因此,在defer函数中可以加入recover起到让函数恢复正常执行的作用

package main

func main() {
    test()
}

func test() {
    defer func() {
        if err := recover(); err != nil {
            println(err.(string)) // 将 interface{} 转型为具体类型。
        }
    }()

    panic("panic error!")
}

参考资料