Go Recover和Panic 组合使用

recover 是一个内置函数,用于重新获得对 panic 程序的控制。

recover 函数原型如下

funcrecover()interface{}

仅当在延迟函数内部调用时,recover 才有用。 在延迟函数内执行恢复调用通过恢复正常执行来停止 panic 序列,并检索传递给 panic 函数调用的错误消息。 如果在延迟函数之外调用 recover,它不会停止panic 序列。

让我们修改一下我们的程序,使用 recover 来恢复 panic 后的正常执行。

package main

import (

"fmt"

)

funcrecoverFullName() {

if r := recover(); r!= nil {

fmt.Println("recovered from ", r)

}

}

funcfullName(firstName *string, lastName *string) {

defer recoverFullName()

if firstName == nil {

panic("runtime error: first name cannot be nil")

}

if lastName == nil {

panic("runtime error: last name cannot be nil")

}

fmt.Printf("%s %s\n", *firstName, *lastName)

fmt.Println("returned normally from fullName")

}

funcmain() {

defer fmt.Println("deferred call in main")

firstName := "Elon"

fullName(&firstName, nil)

fmt.Println("returned normally from main")

}

运行示例

recoveryFullName() 函数调用recover() 返回传递给panic 函数调用的值。 这里我们只是打印了 recovery 返回的值。recoverFullName() 被推迟到fullName 函数。

当 fullName 发生紧急情况时,将调用延迟函数 recoveryName(),该函数使用 recovery() 来停止紧急情况序列。

上面程序输出如下

go 恢复panic程序

当程序发生 panic 时。延迟的 recoverFullName 函数被调用,该函数又调用 recover() 以重新获得对 panic 序列的控制。 对 recovery() 的调用返回传递给 panic() 的参数,因此它打印如下内容

recovered from runtime error: last name cannot be nil

执行 recover() 后,panic 停止,控制权返回给调用者,在这种情况下,是 main函数。 程序从 main 函数的 fmt.Println("returned normally from main") 开始继续正常执行,因为 panic 已经恢复了????。 它打印如下文本

returned normally from main

然后打印

deferred call in main

让我们再看一个例子,我们从访问切片的无效索引引起的panic中恢复正常。

package main

import (

"fmt"

)

funcrecoverInvalidAccess() {

if r := recover(); r != nil {

fmt.Println("Recovered", r)

}

}

funcinvalidSliceAccess() {

defer recoverInvalidAccess()

n := []int{5, 7, 4}

fmt.Println(n[4])

fmt.Println("normally returned from a")

}

funcmain() {

invalidSliceAccess()

fmt.Println("normally returned from main")

}

运行示例

上述程序输出如下

Recovered runtime error: index out of range [4] with length 3

normally returned from main

从输出中,可以了解到我们已经从 panic 中恢复过来。

在 Recover 恢复之后获取堆栈信息

如果我们从 Panic 中恢复过来,我们就会丢失关于panic的堆栈跟踪。 在上述程序中,即使已经恢复了其实是丢失了堆栈跟踪的。

有一种方法,可以使用 Debug 包的 PrintStack 函数打印堆栈跟踪

package main

import (

"fmt"

"runtime/debug"

)

funcrecoverFullName() {

if r := recover(); r != nil {

fmt.Println("recovered from ", r)

debug.PrintStack()

}

}

funcfullName(firstName *string, lastName *string) {

defer recoverFullName()

if firstName == nil {

panic("runtime error: first name cannot be nil")

}

if lastName == nil {

panic("runtime error: last name cannot be nil")

}

fmt.Printf("%s %s\n", *firstName, *lastName)

fmt.Println("returned normally from fullName")

}

funcmain() {

defer fmt.Println("deferred call in main")

firstName := "Elon"

fullName(&firstName, nil)

fmt.Println("returned normally from main")

}

运行示例

在上面的程序中,我们使用 debug.PrintStack() 来打印堆栈信息。

上述程序执行结果如下

go 恢复panic打印堆栈信息

从上面的输出我们可以看到,panic被恢复了,并且打印如下信息

recovered from runtime error: last name cannot be nil

紧接着打印堆栈信息,然后打印如下结果

returned normally from main

deferred call in main

Panic、Recover 和 Goroutines

Recover 仅在从 panic 的同一个 goroutine 中调用时才起作用。 无法从不同 goroutine 中发生的 panic 中恢复过来。 让我们通过一个例子来理解这一点。

package main

import (

"fmt"

)

funcrecovery() {

if r := recover(); r != nil {

fmt.Println("recovered:", r)

}

}

funcsum(a int, b int) {

defer recovery()

fmt.Printf("%d + %d = %d\n", a, b, a+b)

done := make(chanbool)

go divide(a, b, done)

<-done

}

funcdivide(a int, b int, done chanbool) {

fmt.Printf("%d / %d = %d", a, b, a/b)

done <- true

}

funcmain() {

sum(5, 0)

fmt.Println("normally returned from main")

}

运行示例

在上面的程序中,函数divide() 将会发生panic。 因为变量 b 是 nil 并且不可能将一个数除以零。 sum() 函数调用了一个延迟函数 recovery(),该函数用于从 panic 中恢复。 函数divide() 作为一个单独的goroutine 被调用。 我们在变量done的通道上进行等待。 确保divide() 完成执行。

你认为程序的输出是什么。 panic会恢复吗? 答案当然是不。 panic将无法恢复。 这是因为 recover 函数存在于不同的 goroutine 中,而panic发生在不同 goroutine 中的divide() 函数中。 因此是无法恢复的。

上述程序输出如下:

go 不同goroutine中的recover和panic

我们在输出中看到,recover并没有执行。

如果divide() 函数在同一个goroutine 中被调用,我们就会从 panic 中恢复过来。

如果将上面程序中的

go divide(a, b, done)

改为

divide(a, b, done)

recover将会执行,因为它和panic在同一个 goroutine 中。 如果程序按照上面进行更改,则打印结果如下

5 + 0 = 5

recovered: runtime error: integer divide by zero

normally returned from main

到此我们就将 recover 介绍完成了。

本篇除了Recover之外,还涉及到Panic。推荐阅读Go 语言中错误处理的 Panic

本文转载自:迹忆客(https://www.jiyik.com)

以上是 Go Recover和Panic 组合使用 的全部内容, 来源链接: utcz.com/z/290241.html

回到顶部