我如何处理goroutine中的恐慌?

时间:2019-02-06 17:20:35

标签: go

我对golang来说还很陌生。因此,请尽量避免使用剑。

我正在尝试通过研究教程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())
}

更新

我将代码更改为(例如)处理goroutine中的错误,例如(go-playground here

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("")
}

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

但是现在我陷入了僵局。

感谢和问候。
Temporarya
(golang noobie)

1 个答案:

答案 0 :(得分:6)

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

  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正在等待<-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())
}