处理Go协程中的恐慌异常

19

我知道在 Go 中用 recover 处理 panic。但是当 panic 发生在 Go 协程中时,以下代码块无法进行恢复。

func main() {
    done := make(chan int64)
    defer fmt.Println("Graceful End of program")
    defer func() {
     r := recover()
     if _, ok := r.(error); ok {
        fmt.Println("Recovered")
     }
    }()

    go handle(done)
    for {
        select{
        case <- done:
        return
        }
    } 
}

func handle(done chan int64) {
    var a *int64
    a = nil

    fmt.Println(*a)
    done <- *a
}

然而,下面的代码块可以按预期执行

func main() {
    done := make(chan int64)
    defer fmt.Println("Graceful End of program")
    defer func() {
     r := recover()
     if _, ok := r.(error); ok {
        fmt.Println("Recovered")
     }
    }()

    handle(done)
    for {
        select{
        case <- done:
        return
        }
    } 
}

func handle(done chan int64) {
    var a *int64
    a = nil

    fmt.Println(*a)
    done <- *a
}

如何从Go协程中产生的恐慌中恢复。这是游乐场的链接:https://play.golang.org/p/lkvKUxMHjhi

2个回答

39

只有在与panic调用相同的goroutine中调用时,recover才起作用。来自Go博客:

该过程继续向上堆栈,直到当前goroutine中的所有函数都返回,此时程序崩溃

您必须在goroutine中使用deferred recover。

https://blog.golang.org/defer-panic-and-recover

文档/规范也包括同样的内容:
在执行函数F时,对panic的显式调用或运行时panic将终止F的执行。任何被F延迟执行的函数将按照通常方式执行。接下来,运行调用F的函数所延迟执行的函数,以此类推,直到顶层函数所延迟执行的函数结束为止。此时,程序被终止并报告错误条件,其中包括panic的参数值。这种终止序列称为panicking。

https://golang.org/ref/spec#Handling_panics


1
我使用以下方式来处理这种情况,它能够按预期工作。
package main

import (
    "fmt"
    "time"
)

func main() {
    defer fmt.Println("Graceful End of program")
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered:", r)
        }
    }()

    done := make(chan int64)
    var panicVar interface{}

    go handle(done, &panicVar)

WAIT:
    for {
        select {
        case <-done:
            break WAIT // break WAIT: goroutine exit normally
        default:
            if panicVar != nil {
                break WAIT // break WAIT: goroutine exit panicked
            }

            // wait for goroutine exit
        }

        time.Sleep(1 * time.Microsecond)
    }

    if panicVar != nil {
        panic(panicVar) // panic again
    }
}

func handle(done chan int64, panicVar *interface{}) {
    defer func() {
        if r := recover(); r != nil {
            // pass panic variable outside
            *panicVar = r
        }
    }()

    var a *int64
    a = nil

    fmt.Println(*a)
    done <- *a
}

游乐场链接: https://play.golang.org/p/t0wXwB02pa3


不要使用var来存储panic状态,而是使用channels。这样你就可以在主函数中删除sleep和for循环。在这种情况下,仅使用select即可。 - lokanadham100

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接