Golang 并发编程(五):Golang 中 defer、panic、recover 语句详解
- 陈大剩
- 2025-02-05 22:00:57
- 7
defer
在 Go 语言中,defer
用于延迟调用指定函数,它只能出现在函数内部,由 defer
关键字以及某个函数的调用表达式组成。我们可以理解 defer
调入一个函数 执行栈 的过程,然后再依次取出。
简单例子如下:
func printStr() {
defer fmt.Println("函数执行结束前一刻才会被打印。")
fmt.Println("第一个被打印的。")
}
其中
defer
关键字后面是针对fmt.prnintln
函数的通用表达式。上面printStr
为外围函数,调用printStr
的函数为调用函数
defer
具体规则如下:
- 当外围函数中的语句正常执行完毕时,只有其中所有的延迟函数都执行完毕,外围函数才会真正结束执行;
- 当执行外围函数中的 return 语句时,只有其中的所有延迟函数都执行完毕后,外围函数才会真正返回;
- 当外围函数引发运行时恐慌(panic)时,只有其中所以的延迟函数都执行完毕后,该运行恐慌才会真正扩散至调用函数;
因为 defer
语句这样的特性,所以成为执行释放资源或异常处理等收尾任务的首选,明显的优势有两个,如下:
- 对延迟函数的调用总会在外围函数执行结束前执行
defer
语句在外围函数体中位置不限,并且数量不限。
需要注意的三点,如下:
第一点:使用 defer
延迟函数,外围函数中执行顺序不同。
func printNumbers() {
for i := 0; i < 10; i++ {
defer func(n int) {
fmt.Println(n)
}(i)
}
}
这里会输出 9876543210
第二点:同一个外围函数多个延迟函数调用的执行顺序不同。
func printNumbers2() {
for i := 0; i < 10; i++ {
defer fmt.Println(i)
defer fmt.Println(i * 2)
}
}
第三点:延迟函数调用传入参数时,会在当前 defer
语句执行时求出。
func printNumbers3() {
for i := 0; i < 5; i++ {
defer func(n int) {
fmt.Printf("%d \n", n)
}(i * 2)
}
}
也就是说 i*2 会当前
defer
语句执行时求出,最后再依次调出
panic
写过 Go
的朋友都知道,Go
在报告错误的方式是返回一个 error 类型的值,遇到致命错误时,很有可能会使程序无法继续运行。这时我们可以使用 panic
,为了控制运行期间的致命错误,Go
内建了 专用函数 panic
,该函数用于停止当前的控制流程并引发一个恐慌。
具体运行如下
func main() {
output()
}
func output() {
inner()
}
func inner() {
panic(errors.New("预定义故障"))
}
panic
可以接受一个任意类型的参数值,不过这个参数类型常常会是 string
或者 error
,这样更容易显示运行时的恐慌。
panic
也可以是运行时系统来引发,例如:
func main() {
index := 3
ay := [2]int{1, 2}
_ = ay[index]
}
注意:此处相当于显式调用
panic
并传入一个runtime.Error
类型的参数。
recover
我们都不希望程序崩溃,那么 recover
函数 就派上用场了,recover
可以 “拦截” 一个运行时恐慌。运行时恐慌一旦被引发,就会向调用方传播直至程序崩溃,recover
用于从当前恐慌状态中恢复并重新获得流程控制权,recover
函数被调用后,会返回一个 interface{}
类型的结果,如果当时程序正在处于恐慌状态,那么这个结果就会是非 nil
的。
recover
函数一般应该与 defer
语句配合起来使用,例如:
defer func() {
if err := recover(); err != nil {
fmt.Println(err)
}
}()
把此类代码放在函数体开始处,这样可以有效防止该函数及其下层调用中的代码引发运行时恐慌,一旦发现 recover
函数调用结果非 nil
,就立刻采取措施。
func main() {
safeDivision(10, 0) // 触发恐慌
fmt.Println("程序继续执行")
}
func safeDivision(a, b int) {
defer func() {
if r := recover(); r != nil {
fmt.Println("恢复了恐慌:", r)
}
}()
if b == 0 {
panic("除以零错误") // 引发恐慌
}
fmt.Println("结果:", a/b)
}
代码解释:
defer
语句会延迟到最后执行,而recover()
函数会捕获运行时恐慌,所以这里程序会继续执行。