网站建设课程的感受,西安建设网站平台,官方做任务网站,手机网站怎么做淘宝客什么是Panic#xff1f;
在 Go 程序中处理异常情况的惯用方法是使用errors.。errors足以应对程序中出现的大多数异常情况。
**但有些情况下#xff0c;程序在出现异常情况后无法继续执行。在这种情况下#xff0c;我们使用panic提前终止程序。当函数遇到恐慌时#xff0c…什么是Panic
在 Go 程序中处理异常情况的惯用方法是使用errors.。errors足以应对程序中出现的大多数异常情况。
**但有些情况下程序在出现异常情况后无法继续执行。在这种情况下我们使用panic提前终止程序。当函数遇到恐慌时其执行将停止所有延迟的函数都会被执行然后控制权返回给其调用者。这个过程一直持续到当前goroutine的所有函数都返回为止此时程序将打印恐慌消息然后打印堆栈跟踪然后终止。**当我们编写示例程序时这个概念会更加清晰。
**可以重新获得对发生恐慌的程序的控制recover**我们将在本教程的后面讨论这一点。
可以认为panic和recover类似于Java等其他语言中的try-catch-finally习惯用法只不过它们在Go中很少使用。
什么时候应该使用恐慌
一个重要因素是您应该避免恐慌并尽可能恢复和使用错误。只有在程序无法继续执行的情况下才应该使用恐慌和恢复机制。
恐慌有两个有效的用例。
程序无法继续执行的不可恢复错误。 一个示例是 Web 服务器无法绑定到所需端口。在这种情况下恐慌是合理的因为如果端口绑定本身失败就无事可做。程序员错误。 假设我们有一个接受指针作为参数的方法并且有人使用参数调用该方法nil。nil在这种情况下我们可能会感到恐慌因为调用带有参数且期望有效指针的方法是程序员的错误。
恐慌的例子
panic下面提供了内置函数的签名
func panic(interface{})当程序终止时传递给恐慌函数的参数将被打印。当我们编写示例程序时它的用途就会很清楚。所以让我们立即这样做。
我们将从一个人为的例子开始展示恐慌是如何运作的。
package mainimport (fmt
)func fullName(firstName *string, lastName *string) {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)
}func main() {firstName : ElonfullName(firstName, nil)fmt.Println(returned normally from main)
}Run in playground
上面是一个打印一个人的全名的简单程序。第 7 行中的函数fullName。打印一个人的全名。该函数检查firstName和lastName指针是否nil。如果是nil函数调用则panic带有相应的消息。当程序终止时将打印此消息。
运行该程序将打印以下输出
panic: runtime error: last name cannot be nilgoroutine 1 [running]:
main.fullName(0x0?, 0xc00003e730?)/tmp/sandbox3307338859/prog.go:12 0x106
main.main()/tmp/sandbox3307338859/prog.go:20 0x2f让我们分析这个输出以了解恐慌是如何工作的以及程序恐慌时如何打印堆栈跟踪。
在19 行号中。我们分配Elon给firstName. 我们在第 20行中使用调用fullName函数。 因此第 11 行条件的会满足程序会恐慌。当遇到恐慌时程序执行终止传递给恐慌函数的参数将被打印然后是堆栈跟踪。由于程序在第12 行的紧急函数调用之后终止后面的不会被执行。
该程序首先打印传递给函数的消息panic
panic: runtime error: last name cannot be nil然后打印堆栈跟踪。
该程序在第12 行出现恐慌。因此
goroutine 1 [running]:
main.fullName(0xc00006af58, 0x0)/tmp/sandbox210590465/prog.go:12 0x193将首先打印。然后将打印堆栈中的下一项。在我们的例子中在20行fullName调用的地方是堆栈跟踪中的下一项。因此接下来会打印它。
main.main()/tmp/sandbox210590465/prog.go:20 0x4d现在我们已经到达导致恐慌的顶级函数并且上面没有更多级别因此没有更多内容可打印。
再举一个例子
恐慌也可能是由运行时发生的错误引起的例如尝试访问切片中不存在的索引。
让我们编写一个人为的示例该示例会因越界切片访问而产生恐慌。
package mainimport (fmt
)func slicePanic() {n : []int{5, 7, 4}fmt.Println(n[4])fmt.Println(normally returned from a)
}
func main() {slicePanic()fmt.Println(normally returned from main)
}
Run in playground
在上面的程序中 我们正在尝试访问切片n[4]中的无效索引。该程序将出现以下输出
panic: runtime error: index out of range [4] with length 3goroutine 1 [running]:
main.slicePanic()/tmp/sandbox942516049/prog.go:9 0x1d
main.main()/tmp/sandbox942516049/prog.go:13 0x22恐慌期间推迟通话
让我们回忆一下恐慌的作用。当函数遇到恐慌时其执行将停止所有延迟的函数都会被执行然后控制权返回给其调用者。这个过程一直持续到当前 goroutine 的所有函数都返回为止此时程序将打印恐慌消息然后打印堆栈跟踪然后终止。
在上面的示例中我们没有推迟任何函数调用。如果存在延迟函数调用则会执行该函数然后将控制权返回给其调用者。
让我们稍微修改一下上面的示例并使用 defer 语句。
package mainimport (fmt
)func fullName(firstName *string, lastName *string) {defer fmt.Println(deferred call in fullName)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)
}func main() {defer fmt.Println(deferred call in main)firstName : ElonfullName(firstName, nil)fmt.Println(returned normally from main)
}
Run in playground
所做的唯一更改是在第 8 和 20行中添加了延迟函数调用。
该程序打印
deferred call in fullName
deferred call in main
panic: runtime error: last name cannot be nilgoroutine 1 [running]:
main.fullName(0xc00006af28, 0x0)/tmp/sandbox451943841/prog.go:13 0x23f
main.main()/tmp/sandbox451943841/prog.go:22 0xc6当程序在第13行发生恐慌时。首先执行任何延迟函数调用然后控制权返回到执行延迟调用的调用者依此类推直到到达顶层调用者。
在我们的例子中defer在第 8行的声明。首先执行fullName函数。这将打印以下消息。
deferred call in fullName然后控制返回到main执行延迟调用的函数因此打印
deferred call in main现在控件已到达顶层函数因此程序会打印紧急消息然后打印堆栈跟踪然后终止。
从恐慌中恢复
恢复是一个内置函数用于重新获得对发生恐慌的程序的控制。
下面提供了恢复函数的签名
func recover() interface{}仅当在延迟函数内部调用时恢复才有用。在延迟函数内执行恢复调用可以通过恢复正常执行来停止恐慌序列并检索传递给恐慌函数调用的错误消息。如果在延迟函数之外调用恢复它不会停止恐慌序列。
让我们修改我们的程序并使用recover在panic之后恢复正常执行。
package mainimport (fmt
)func recoverFullName() {if r : recover(); r ! nil {fmt.Println(recovered from , r)}
}func fullName(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)
}func main() {defer fmt.Println(deferred call in main)firstName : ElonfullName(firstName, nil)fmt.Println(returned normally from main)
}
Run in playground
当发生恐慌时将调用fullName延迟函数来停止恐慌
该程序将打印
recovered from runtime error: last name cannot be nil
returned normally from main
deferred call in main当程序在第 19行发生恐慌时延迟recoverFullName函数被调用该函数又调用recover()以重新获得对恐慌序列的控制。呼叫行recover() 返回传递给的参数panic()并因此打印
recovered from runtime error: last name cannot be nil执行后recover()恐慌停止控制权返回给调用者在本例中为main函数。main由于恐慌已恢复程序从第 29 行开始继续正常执行。它打印returned normally from main 后跟deferred call in main
让我们再看一个示例其中我们从由于访问切片的无效索引而引起的恐慌中恢复。
package mainimport (fmt
)func recoverInvalidAccess() {if r : recover(); r ! nil {fmt.Println(Recovered, r)}
}func invalidSliceAccess() {defer recoverInvalidAccess()n : []int{5, 7, 4}fmt.Println(n[4])fmt.Println(normally returned from a)
}func main() {invalidSliceAccess()fmt.Println(normally returned from main)
}
Run in playground
运行上面的程序将输出
Recovered runtime error: index out of range [4] with length 3
normally returned from main从输出中您可以了解到我们已经从恐慌中恢复过来。
恢复后获取堆栈跟踪
如果我们从恐慌中恢复我们就会丢失有关恐慌的堆栈跟踪。即使在恢复后的上面的程序中我们也丢失了堆栈跟踪。
有一种方法可以使用Debug包的PrintStack函数打印堆栈跟踪
package mainimport (fmtruntime/debug
)func recoverFullName() {if r : recover(); r ! nil {fmt.Println(recovered from , r)debug.PrintStack()}
}func fullName(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)
}func main() {defer fmt.Println(deferred call in main)firstName : ElonfullName(firstName, nil)fmt.Println(returned normally from main)
}
Run in playground
在上面的程序中我们使用debug.PrintStack()在第 11 行来打印堆栈跟踪。
该程序将打印
recovered from runtime error: last name cannot be nil
goroutine 1 [running]:
runtime/debug.Stack(0x37, 0x0, 0x0)/usr/local/go-faketime/src/runtime/debug/stack.go:24 0x9d
runtime/debug.PrintStack()/usr/local/go-faketime/src/runtime/debug/stack.go:16 0x22
main.recoverFullName()/tmp/sandbox771195810/prog.go:11 0xb4
panic(0x4a1b60, 0x4dc300)/usr/local/go-faketime/src/runtime/panic.go:969 0x166
main.fullName(0xc0000a2f28, 0x0)/tmp/sandbox771195810/prog.go:21 0x1cb
main.main()/tmp/sandbox771195810/prog.go:30 0xc6
returned normally from main
deferred call in main从输出中您可以了解到恐慌已恢复并被recovered from runtime error: last name cannot be nil打印。接下来打印堆栈跟踪。然后恐慌恢复后打印
returned normally from main
deferred call in main恐慌、恢复和 Goroutine
仅当从同一个发生恐慌的goroutine调用时Recover 才起作用。**不可能从不同 goroutine 中发生的恐慌中恢复。**让我们通过一个例子来理解这一点。
package mainimport (fmt
)func recovery() {if r : recover(); r ! nil {fmt.Println(recovered:, r)}
}func sum(a int, b int) {defer recovery()fmt.Printf(%d %d %d\n, a, b, ab)done : make(chan bool)go divide(a, b, done)-done
}func divide(a int, b int, done chan bool) {fmt.Printf(%d / %d %d, a, b, a/b)done - true}func main() {sum(5, 0)fmt.Println(normally returned from main)
}
Run in playground
在上面的程序中该函数divide()将在第 22 行发生恐慌。因为 b 为零并且不可能将数字除以零。该sum()函数调用一个延迟函数recovery()用于从恐慌中恢复。该函数divide()在第 1 7行作为单独的 goroutine 被调用。 我们在18行号的done通道上等待。确保divide()完成执行。
你认为该程序的输出是什么恐慌情绪会恢复吗答案是不。恐慌将无法恢复。这是因为该recovery函数存在于不同的 goroutine 中并且恐慌发生在divide()不同 goroutine 中的函数中。因此不可能恢复。
运行该程序将打印
5 0 5
panic: runtime error: integer divide by zerogoroutine 18 [running]:
main.divide(0x5, 0x0, 0xc0000a2000)/tmp/sandbox877118715/prog.go:22 0x167
created by main.sum/tmp/sandbox877118715/prog.go:17 0x1a9您可以从输出中看到恢复尚未发生。
如果该divide()函数在同一个 goroutine 中调用我们就会从恐慌中恢复过来。
如果17 行号程序修改为
go divide(a, b, done)到
divide(a, b, done)由于恐慌发生在同一个 goroutine 中因此恢复将会发生。如果程序在进行上述更改后运行它将打印
5 0 5
recovered: runtime error: integer divide by zero
normally returned from main