【问题标题】:How do I handle panic in goroutines?如何处理 goroutine 中的恐慌?
【发布时间】:2019-02-07 01:58:57
【问题描述】:

我对@9​​87654327@很陌生。所以,请给我一把剑(如果可能的话)。

我试图通过学习教程here从网络获取数据

现在,教程一切顺利,但我想检查边缘情况和错误处理(只是为了彻底了解我对语言的新学习,不想成为知识不成熟的人) .

这是我的go-playground code

在询问之前,我查看了很多参考资料,例如:

  1. Go blog defer,panic and recover
  2. handling panics in goroutines
  3. how-should-i-write-goroutine

还有一些,但我想不通。

如果你不想去操场,这里是代码(原因未知):

// MakeRequest : Makes requests concurrently
func MakeRequest(url string, ch chan<- string, wg *sync.WaitGroup) {
    start := time.Now()
    resp, err := http.Get(url)
    defer func() {
        resp.Body.Close()
        wg.Done()
            if r := recover(); r != nil {
                fmt.Println("Recovered in f", r)
            }
    }()
    if err != nil {
        fmt.Println(err)
        panic(err)
    }
    secs := time.Since(start).Seconds()
    body, _ := ioutil.ReadAll(resp.Body)

    ch <- fmt.Sprintf("%.2f elapsed with response length: %d %s", secs, len(body), url)
}

func main() {
    var wg sync.WaitGroup
    output := []string{
        "https://www.facebook.com",
        "",
    }
    start := time.Now()
    ch := make(chan string)
    for _, url := range output {
        wg.Add(1)
        go MakeRequest(url, ch, &wg)
    }

    for range output {
        fmt.Println(<-ch)
    }
    fmt.Printf("%.2fs elapsed\n", time.Since(start).Seconds())
}

更新

我将代码更改为(比方说)像这样(go-playground here)处理 goroutine 中的错误:

func MakeRequest(url string, ch chan<- string, wg *sync.WaitGroup) {
    start := time.Now()
    resp, err := http.Get(url)

    if err == nil {
        secs := time.Since(start).Seconds()
        body, _ := ioutil.ReadAll(resp.Body)

        ch <- fmt.Sprintf("%.2f elapsed with response length: %d %s", secs, len(body), url)
        // fmt.Println(err)
        // panic(err)
    }
    defer wg.Done()
}

更新 2:

在得到答复后,我将代码更改为此并成功消除了chan 死锁,但是现在我需要在main 中处理这个问题:

func MakeRequest(url string, ch chan<- string, wg *sync.WaitGroup) {
    defer wg.Done()
    start := time.Now()
    resp, err := http.Get(url)

    if err == nil {
        secs := time.Since(start).Seconds()
        body, _ := ioutil.ReadAll(resp.Body)

        ch <- fmt.Sprintf("%.2f elapsed with response length: %d %s", secs, len(body), url)
        // fmt.Println(err)
        // panic(err)
    }
    // defer resp.Body.Close()
    ch <- fmt.Sprintf("")
}

难道没有更优雅的方式来处理这个问题吗?

但现在我陷入了僵局。

感谢和问候。
临时工
(一个 golang 菜鸟)

【问题讨论】:

  • 您要解决的具体问题是什么?
  • 恐慌也不例外,它们旨在表明程序出现问题并崩溃。
  • 如果 err 不是 nil,resp 是。由于 nil 指针取消引用,您的延迟函数将 再次 恐慌。
  • 对不起,大家对我的跛脚,我只想在出现错误的任何其他情况下安全退出 goroutine,仅此而已:)
  • 如果没有错误,您的 Update 2 代码将死锁!成功后,它会尝试发送两次消息。操场总是失败,这就是它在那里工作正常的原因。

标签: go


【解决方案1】:

您正在正确使用恢复。你有两个问题:

  1. 您错误地使用了恐慌。只有在出现编程错误时才应该恐慌。避免使用恐慌,除非您认为关闭程序是对所发生事件的合理回应。在这种情况下,我只会返回错误,而不是恐慌。

  2. 您在恐慌中感到恐慌。发生的事情是,您首先在panic(err) 感到恐慌。然后在你的延迟函数中,你对resp.Body.Close() 感到恐慌。当 http.Get 返回错误时,它返回 nil 响应。这意味着 resp.Body.Close() 作用于 nil 值。

处理这个问题的惯用方法如下:

func MakeRequest(url string, ch chan<- string, wg *sync.WaitGroup) {
    defer wg.Done()
    start := time.Now()
    resp, err := http.Get(url)
    if err != nil {
        //handle error without panicing
    }
    // there was no error, so resp.Body is guaranteed to exist.
    defer resp.Body.Close()
    ...

更新响应:如果http.Get() 返回错误,则您永远不会在频道上发送。在某些时候,除了主 goroutine 之外的所有 goroutine 都停止运行,并且主 goroutine 正在等待 &lt;-ch。由于该通道接收永远不会完成,并且 Go 运行时没有其他任何东西可以安排,它会发生恐慌(不可恢复)。


对评论的回应:为确保频道不会挂起,您需要进行某种协调以了解消息何时停止发送。这如何实现取决于您的实际程序,并且示例不一定可以推断为现实。对于这个例子,我会在 WaitGroup 完成后简单地关闭通道。

Playground

func main() {
    var wg sync.WaitGroup
    output := []string{
        "https://www.facebook.com",
        "",
    }
    start := time.Now()
    ch := make(chan string)
    for _, url := range output {
        wg.Add(1)
        go MakeRequest(url, ch, &wg)
    }

    go func() {
        wg.Wait()
        close(ch)
    }()

    for val := range ch {
        fmt.Println(val)
    }
    fmt.Printf("%.2fs elapsed\n", time.Since(start).Seconds())
}

【讨论】:

  • 那么,我如何处理那个chan不挂起,我认为可能有两种方法,首先我发送一些东西,即使出现错误,或者我应该将main中的代码更改为以不同的方式处理来自 chan 的输入,对吗?如果是这样,您能否举一些相同的例子,这将是很大的帮助。谢谢。
  • 是的,绝对是don't panic @temporarya,这是go-proverbs之一
  • @Stephen Weinberg 为什么要创建一个新的 goroutine func() 来处理 wg.Wait() ,为什么不在主线程本身做呢?
  • 试试看。如果您正在等待等待组,则您没有在频道上收听。如果你不在频道上收听,你会死锁。
猜你喜欢
  • 1970-01-01
  • 2018-10-28
  • 2018-05-28
  • 2021-07-23
  • 2015-03-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多