我如何处理goroutines中的恐慌?

tem*_*rya 2 go

我很新golang.所以,请把剑给我(如果可能的话).

我试图通过在这里学习教程从网上获取数据

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

这是我的游乐场代码.

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

  1. 去博客推迟,恐慌和恢复
  2. 处理goroutines中的恐慌
  3. 如何-应该-I-写够程

还有一些,但我无法弄明白.

这是代码,以防您不想去操场(原因尚不为人所知):

// 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())
}
Run Code Online (Sandbox Code Playgroud)

更新

我改变了代码(让我们说)处理像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()
}
Run Code Online (Sandbox Code Playgroud)

更新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("")
}
Run Code Online (Sandbox Code Playgroud)

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

但现在我陷入了僵局.

感谢致敬.
Temporarya
(golang noobie)

Ste*_*erg 6

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

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

  2. 你在恐慌期间惊慌失措.发生了什么事情,你是第一次惊慌失措panic(err).然后在你的延迟函数中,你正在惊慌失措resp.Body.Close().当http.Get返回错误时,它返回一个nil响应.这意味着它resp.Body.Close()的作用是零值.

解决这个问题的惯用方法如下:

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()
    ...
Run Code Online (Sandbox Code Playgroud)

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


对评论的回应:为了确保频道不会挂起,您需要某种协调来了解消息何时停止.如何实现这取决于你的真实程序,一个例子不一定能推断到现实.对于这个例子,我只是在WaitGroup完成时关闭通道.

操场

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())
}
Run Code Online (Sandbox Code Playgroud)

  • 试试吧.如果您正在等待等待组,则表示您没有在通道上收听.如果您没有在频道上收听,您将陷入僵局. (2认同)