In-depth understanding of functions in Go language [exception handling panic] 13

Article directory


exception handling

Golang has no structured exceptions, use panic to throw errors, and recover to catch errors.

A brief description of the usage scenario of the exception: a panic exception can be thrown in Go, and then the exception can be caught by recover in defer, and then processed normally.

panic:

    1、内置函数
    2、假如函数F中书写了panic语句,会终止其后要执行的代码,在panic所在函数F内如果存在要执行的defer函数列表,按照defer的逆序执行
    3、返回函数F的调用者G,在G中,调用函数F语句之后的代码不会执行,假如函数G中存在要执行的defer函数列表,按照defer的逆序执行
    4、直到goroutine整个退出,并报告错误

recover:

    1、内置函数
    2、用来控制一个goroutine的panicking行为,捕获panic,从而影响应用的行为
    3、一般的调用建议
        a). 在defer函数中,通过recever来终止一个goroutine的panicking过程,从而恢复正常代码的执行
        b). 可以获取通过panic传递的error

Notice:

    1.利用recover处理panic指令,defer 必须放在 panic 之前定义,另外 recover 只有在 defer 调用的函数中才有效。否则当panic时,recover无法捕获到panic,无法防止panic扩散。
    2.recover 处理异常后,逻辑并不会恢复到 panic 那个点去,函数跑到 defer 之后的那个点。
    3.多个 defer 会形成 defer 栈,后定义的 defer 语句会被最先调用。

package main

func main() {
test()
}

func test() { defer func() { if err := recover(); err != nil { println(err.(string)) // Convert interface{} to a concrete type. } }()




panic("panic error!")

}
output result:

panic error! 

Since the parameter type of panic and recover is interface{}, any type of object can be thrown.

func panic(v interface{})
func recover() interface{}

Sending data to a closed channel will cause a panic

package main

import (
“fmt”
)

func main() {
defer func() {
if err := recover(); err != nil {
fmt.Println(err)
}
}()

var ch chan int = make(chan int, 10)
close(ch)
ch <- 1

}
output result:

send on closed channel

Errors thrown in a deferred call can be caught by subsequent deferred calls, but only the last error can be caught.

package main

import “fmt”

func test() {
defer func() {
fmt.Println(recover())
}()

defer func() {
    panic("defer panic")
}()

panic("test panic")

}

func main() { test() } output:


defer panic 

The catch function recover terminates the error only if called directly inside the deferred call, otherwise it always returns nil. Any uncaught errors are propagated up the call stack.

package main

import “fmt”

func test() { defer func() { fmt.Println(recover()) //effective }() defer recover() //invalid! defer fmt.Println(recover()) //Invalid! defer func() { func() { println("defer inner") recover() // invalid! }() }()










panic("test panic")

}

func main() { test() } output:


defer inner
<nil>
test panic

Using deferred anonymous functions or something like the following are both valid.

package main

import (
“fmt”
)

func except() {
fmt.Println(recover())
}

func test() {
defer except()
panic(“test panic”)
}

func main() { test() } output result:


test panic

If you need to protect the code segment, you can refactor the code block into an anonymous function, which ensures that subsequent code is executed.

package main

import “fmt”

func test(x, y int) {
var z int

func() {
    defer func() {
        if recover() != nil {
            z = 0
        }
    }()
    panic("test panic")
    z = x / y
    return
}()

fmt.Printf("x / y = %d\n", z)

}

func main() { test(2, 1) } output result:


x / y = 0

In addition to using panic to cause interrupting errors, an error object of type error can also be returned to represent the status of the function call.

type error interface { Error() string } The standard library errors.New and fmt.Errorf functions are used to create error objects implementing the error interface. Determine the specific error type by judging the error object instance.


package main

import (
“errors”
“fmt”
)

var ErrDivByZero = errors.New(“division by zero”)

func div(x, y int) (int, error) {
if y == 0 {
return 0, ErrDivByZero
}
return x / y, nil
}

func main() { defer func() { fmt.Println(recover()) }() switch z, err := div(10, 0); err { case nil : println ( z ) case ErrDivByZero : panic ( err ) } } logical value : .










division by zero

Go implements exception handling similar to try catch

package main

import “fmt”

func Try(fun func(), handler func(interface{})) {
defer func() {
if err := recover(); err != nil {
handler(err)
}
}()
fun()
}

func main() { Try(func() { panic(“test panic”) }, func(err interface{}) { fmt.Println(err) }) } Output result:






test panic

How to distinguish between using panic and error?

The convention is: use panic for those that cause irreparable errors in key processes, and use error for others.


  The go language does not have try catch, it advocates returning error.

func divide(a, b int) (int, error) {
    
    
    if b == 0 {
    
    
        return -1, errors.New("divide by zero")
    }
    return a / b, nil
}
if res, err := divide(3, 0); err != nil {
    
    //函数调用方判断error是否为nil
    fmt.Println(err.Error())
    fmt.Println(res)
}
divide by zero
-1

  The Go language defines the error interface, and the custom error must implement the Error() method.

type PathError struct {
    
        //自定义error
    path string
    op string
    createTime string
    message string
}
func (err PathError) Error() string {
    
        //error接口要求实现Error() string方法
	return err.createTime + ": " + err.op + " " + err.path + " " + err.message
}

When will panic occur:

  • Runtime errors will cause panic, such as array out of bounds, division by 0.
  • The program actively calls panic(error).

What will panic do:

  1. Execute the defer chain of the current goroutine in reverse order (recover intervenes here).
  2. Print error message and call stack.
  3. Call exit(2) to end the entire process.
func soo() {
    
    
	fmt.Println("enter soo")

	defer func() {
    
     //去掉这个defer试试,看看panic的流程。把这个defer放到soo函数末尾试试
		//recover必须在defer中才能生效
		if err := recover(); err != nil {
    
    
			fmt.Printf("soo函数中发生了panic:%s\n", err)
		}
	}()
	fmt.Println("regist recover")

	defer fmt.Println("hello")
	defer func() {
    
    
		n := 0
		_ = 3 / n //除0异常,发生panic,下一行的defer没有注册成功
		defer fmt.Println("how are you")
	}()
}
enter soo
regist recover
hello
soo函数中发生了panic:runtime error: integer divide by zero
--------
enter soo
regist recover
hello
panic
--------
enter soo
regist recover
hello
panic

Guess you like

Origin blog.csdn.net/m0_52896752/article/details/130235165