go - 我如何处理 goroutine 中的 panic ?

标签 go

我是 golang 的新手。所以,请把剑留给我(如果可能的话)。

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

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

这是我的 go-playground code .

在提问之前我看了很多引用资料,比如:

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

还有一些,但是我想不通。

如果你不想去 Playground (出于人类未知的原因),这是代码:

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

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

但现在我陷入了僵局。

感谢和问候。
暂时的
(一个 golang 菜鸟)

最佳答案

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

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

  2. 你在 panic 中 panic 。发生的事情是您首先在 panic(err) 处 panic .然后在你的延迟函数中,你在 resp.Body.Close() 处 panic .当 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()返回错误,您永远不会在 channel 上发送。在某个时候,除了主 goroutine 之外的所有 goroutine 都停止运行并且主 goroutine 正在等待 <-ch .由于该 channel 接收永远不会完成,并且 Go 运行时没有其他可调度的东西,它会发生 panic (不可恢复)。


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

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

关于go - 我如何处理 goroutine 中的 panic ?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54559189/

相关文章:

go - 如何在 gRPC Protobuf 中创建接口(interface)类型?

postgresql - 从 Postgres 扫描到带有指针的结构体

go - 从指数和尾数创建 float

package - 子包中的可见性

go - 如何在 Golang 中运行数据存储 GQL 查询?

mongodb - 总是产生相同哈希的无序 2 字符串哈希函数

golang 在子目录中找不到包 gin

Goland自动格式化

go - 如何在具有自定义传输的客户端上使用go-retryablehttp?

go - 如何摆脱在自动完成后VSCode的intellisense无法正常工作后出现的烦人文本框?