01
介绍
在 Golang 语言中,我们可以在函数(自定义和部分内置)或方法中使用 defer 关键字注册延迟调用(一个或多个),多个延迟调用的执行顺序是先进后出(FILO)。并且不会受到函数执行结束退出,显式调用 return 和主动(或被动)触发 panic 的影响,注册成功的所有延迟调用都会被执行,除非 defer 注册在 return 之后或者函数(或方法)调用 os.Exit(1)
。
defer 注册多个延迟调用,执行顺序是先进后出(FILO)。
示例代码:
func main () {
defer func() {
fmt.Println("A")
}()
defer func() {
fmt.Println("B")
}()
fmt.Println("main goroutine run over")
// panic("this is a panic example")
// return
}
defer 如果定义在 return 之后,它等于 defer 没有注册,将不会执行。
示例代码:
func main () {
fmt.Println("main")
return
defer func() {
fmt.Println("A")
}()
}
defer 所在的函数或方法中,如果调用 os.Exit(1)
,defer 即便注册,也不会执行。
示例代码:
func main () {
defer func() {
fmt.Println("A")
}()
fmt.Println("main")
os.Exit(1)
}
defer 必须在函数和方法中才可以使用,并且 defer 后面必须是函数(自定义和部分内置函数)或方法,defer 函数的实参是值拷贝。
示例代码
func main () {
a := 0
defer func(num int) {
fmt.Println("defer func()", num)
}(a)
a++
fmt.Println(a)
}
02
使用场景
使用关键字 defer 注册的函数(自定义和部分内置)或方法,因为不会受到函数执行结束,显式调用 return 和主动(或被动)触发 panic 的影响,通常会用于防止忘记释放资源和捕获 panic(同一 goroutine 中) 防止应用程序崩溃退出的应用场景。
示例代码:
func main () {
f, err := os.OpenFile("text.txt", os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0755)
if err != nil {
fmt.Println(err)
}
defer f.Close()
n, err := f.WriteString("this is a text file\t")
if err != nil {
fmt.Println(err)
}
fmt.Println(n)
}
阅读上面这段代码,我们使用 defer 延迟调用释放资源,防止忘记释放资源(关闭文件或解锁),通常 defer 会放在错误检查之后。
示例代码:
func main () {
defer func() {
if err := recover(); err != nil {
fmt.Println("this is a panic" )
}
}()
panic("this is a test panic")
fmt.Println("main")
}
阅读上面这段代码,我们使用 defer 配合 recover 函数,用于拦截 panic(同一 goroutine 中),防止程序崩溃退出。
03
注意事项
虽然使用 defer 具有可以用于防止忘记释放资源和拦截 panic(同一 goroutine 中)防止应用程序崩溃退出等好处。
但是 defer 也有副作用,它会使资源延迟释放,defer 尽量不要再 for-loop 中使用,并且相比于未使用 defer 调用的函数(自定义和部分内置)或方法,defer 也有一定的性能损耗,Golang 语言官方也在 golang 1.13 和 golang 1.14 中优化了 defer 的性能。
相比于 defer 的性能损耗,defer 带来的使代码更加优雅、可读和健壮等优势,我认为 defer 综合来看,利大于弊,它可以给 gopher 们带来的收益比付出的代价更大。所以,我建议大家尽量使用 defer。
还有一点需要注意的是,我们不要使用 defer 调用有返回值的自定义函数或方法,返回值会丢失,可能会给应用程序带来意想不到的错误。
04
总结
本文我们介绍了 defer 的执行机制,使用场景和注意事项,并且给出了相应的示例代码。通常我们会在 Golang 语言开发中使用 defer 防止忘记释放资源(关闭文件或解锁)和捕获 panic(同一 goroutine 中) 防止应用程序崩溃退出。