4.8. 宕机恢复-recover

由于 panic、recover 参数类型为 interface{},因此可抛出任何类型对象。

func panic(v interface{})

func recover() interface{}

向已关闭的通道发送数据会引发panic

package main

import "fmt"

func main() {
    defer func() {
        if err :=recover();err !=nil {
            fmt.Println(err)    //send on closed channel
        }
    }()
    // 创建通道
    var ch chan int = make(chan int,10 )
    // 关闭通道
    close(ch)
    ch <- 1     // 向通道里面传入一个值
}

延迟调用中引发的错误,可被后续延迟调用捕获,但仅最后一个错误可被捕获。

package main

import "fmt"

func main() {
    defer func() {
        fmt.Println(recover())      //defer panic
    }()

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

    panic("panic.....")
}

Go实现类似 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")     //test panic
    }, func(err interface{}) {
        fmt.Println(err)
    })
}

无论是代码运行错误由Runtime层抛出的panic崩溃,还是主动触发的panic崩溃, 都可以配合defer和recover实现错误捕捉和恢复,让代码在发送崩溃后允许继续运行。

在其他语言中,可以通过try/catch机制捕获异常,没有捕获的严重异常会导致宕机,
捕获的异常可以忽略,让代码继续运行。
Go没有异常系统,使用panic触发宕机类似于其他语言的排除异常,

那么recover的宕机恢复机制就对应try/catch机制。

4.8.1. 1.让程序在崩溃时继续执行

package main

import (
    "fmt"
    "runtime"
)

// 崩溃时需要传递的上下文信息,声明错误的结构体,成员保存错误的执行函数
type panicContext struct {
    function string // 所在函数
}

// 保护方式允许一个函数
func ProtectRun(entry func()) {
    // 延迟处理的函数,defer将闭包延迟执行,当panic触发崩溃时,ProtectRun()函数将结束运行,此时defer后的闭包将会调用
    defer func() {
        // 发生宕机时,获取panic传递的上下文并打印
        err := recover()                // recover()获取painc传入的参数
        switch err.(type) {             // 使用switch对err变量进行类型断言
        case runtime.Error:             // 运行时错误
            fmt.Println("runtime error:", err)
        default: // 非运行时错误
            fmt.Println("error:", err)
        }
    }()
    entry()
}
func main() {
    fmt.Println("运行前")

    // 允许一段手动触发的错误
    ProtectRun(func() {
        fmt.Println("手动宕机前")
        // 使用panic传递上下文,将一个结构体附带信息传递过去,此时,recover获取结构体信息,并打印出来
        panic(&panicContext{"手动触发 panic",})
        fmt.Println("手动宕机后")
    })

    // 故意造成空指针访问错误
    ProtectRun(func() {
        fmt.Println("赋值宕机前")
        var a *int
        *a = 1
        fmt.Println("赋值宕机后")
    })
    fmt.Println("运行后")

}

//运行前
//手动宕机前
//error: &{手动触发 panic}
//赋值宕机前
//runtime error: runtime error: invalid memory address or nil pointer dereference
//运行后

4.8.2. 2. panic和recover的关系

panic与defer组合有如下几个特性:

  • 有panic没有cover程序宕机。

  • 有panic也有conver捕获,程序不会宕机。执行完对应的defer后,从宕机点退出当前函数后继续执行。

  • 在painc触发的defer函数内,可以继续调用panic,进一步将错误外抛直到程序整体崩溃。