什么是 painc?
Panic 是 Go 语言中一个内置函数,它会中断正常的控制流并开始 panic 流程。当函数 F 调用 panic 时,F 的执行停止,F 中的任何延迟函数(deferred function)都被正常执行,然后 F 返回给它的调用者。对于调用者来说,F 的行为就像对 panic 的调用。这个过程继续在堆栈中进行,直到当前 goroutine 中的所有函数都返回,这时程序就会崩溃。painc 可以通过直接调用 panic 函数来启动,也可以由运行时错误引起,如数组越界。
简单地说,painc 使一个函数不执行其预期的流程,并可能导致整个程序退出。
解决方案 Go 原生提供了一些功能,可以帮助我们从这种情况下恢复。
Defer Go 的 defer 语句安排了一个函数:这个函数在执行 defer
的函数返回之前立即运行。
我们称 defer 调用的函数为:延迟函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 func Contents (filename string ) (string , error) { f, err := os.Open(filename) if err != nil { return "" , err } defer f.Close() var result []byte buf := make ([]byte , 100 ) for { n, err := f.Read(buf[0 :]) result = append (result, buf[0 :n]...) if err != nil { if err == io.EOF { break } return "" , err } } return string (result), nil }
Recover 当 panic
被调用时,它立即停止执行当前函数,并沿 goroutine 的堆栈运行所有延迟函数。
对 recover
的调用会终止 panic,并返回传递给 panic
的参数。recover
只在延迟函数中有效,因为 panic 后唯一能够运行的代码在延迟函数中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 func server (workChan <-chan *Work) { for work := range workChan { go safelyDo(work) } } func safelyDo (work *Work) { defer func () { if err := recover (); err != nil { log.Println("work failed:" , err) } }() do(work) }
实现 让我们来实现一个简单的数学函数,它可以将两个数字相除,如果分母是 0,就会 panic Divide by zero error!
。
下边的函数检查分母的值,如果它是 0,就会 panic。
1 2 3 4 5 func checkForError (y float64 ) { if y == 0 { panic ("Divident cannot be 0! Divide by 0 error." ) } }
下边这个函数负责对提供的数字进行除法操作并返回,同时它使用上面定义的函数来检查分母是否为 0。
由于 checkForError
会破坏流程,因此这个函数实现了recover()
和defer
,以便在发生 panic 时返回 0。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 func safeDivision (x, y float64 ) float64 { var returnValue float64 defer func () { if err := recover (); err != nil { fmt.Println("Panic occured:" , err) fmt.Println("Returning safe values" ) returnValue = 0 } }() checkForError(y) returnValue = x / y return returnValue }
将上边的代码组合起来:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 package mainimport ( "fmt" ) func main () { fmt.Println("Pre panic execution" ) value1 := safeDivision(2 , 0 ) fmt.Println("Post panic execution, -> " , value1) fmt.Println("Pre valid execution" ) value2 := safeDivision(2 , 1 ) fmt.Println("Post valid execution, value -> " , value2) } func safeDivision (x, y float64 ) float64 { var returnValue float64 defer func () { if err := recover (); err != nil { fmt.Println("Panic occured:" , err) fmt.Println("Returning safe values" ) returnValue = 0 } }() checkForError(y) returnValue = x / y return returnValue } func checkForError (y float64 ) { if y == 0 { panic ("Divident cannot be 0! Divide by 0 error." ) } }
输出为:
1 2 3 4 5 6 Pre panic execution Panic occured: Divident cannot be 0! Divide by 0 error. Returning safe values Post panic execution, -> 0 Pre valid execution Post valid execution, value -> 2