如何等待一个惊慌失措的goroutine呢?

l3zydbqr  于 2022-12-07  发布在  Go
关注(0)|答案(3)|浏览(116)

一种常见的等待goroutine的方法是使用*sync.WaitGroup

func main() {
    wg := &sync.WaitGroup{}
    wg.Add(1)
    go func() {
        defer wg.Done()
        // Long running task
    }()
    wg.Wait()
}

这里没问题。不过,这个怎么样:

func main() {
    wg := &sync.WaitGroup{}
    wg.Add(1)
    go func() {
        defer wg.Done()
        // Long running task
        panic("Something unexpected happened.")
    }()
    wg.Wait()
}

在这种情况下,当wg.Done()被调用时,我相信main()可能会退出,而panic()的细节不会被写入stdout/stderr。这是真的吗?如果是,我该如何防止它发生?

wkyowqbh

wkyowqbh1#

无论如何,panic都会杀死这个进程,因为没有人会从这个进程中恢复过来。如果你想在一个goroutine中从崩溃中恢复过来,你必须让recover在同一个goroutine中 Package 调用栈。
wg.Done将被defer语句调用,但是进程可能在主goroutine完成wg.Wait之前就已经死了。

ttcibm8c

ttcibm8c2#

@伊莱·本德斯基是对的。
请参考src/builtin/builtin.go
内置函数panic会停止当前goroutine的正常执行。当函数F调用panic时,函数F的正常执行会立即停止。任何被函数F延迟执行的函数都会以正常的方式运行,然后函数F返回给调用者。对于调用者G来说,调用函数F就像调用panic一样。终止G的执行并运行所有被延迟的函数,直到执行中的goroutine中的所有函数都按相反的顺序停止为止,此时程序终止并报告错误状态。包括参数的值。这种终止序列称为panicing,可以由内置函数recover控制。
panic之后,将调用defer函数。
在操场上检查这个:https://play.golang.org/p/yrXkEbE1Af7

package main

import (
    "sync"
    "fmt"
)

func main() {
    wg := &sync.WaitGroup{}
    wg.Add(1)
    go func() {
        defer func(){
            fmt.Println("expected to be called after panic")
            wg.Done()
        }()
        // Long running task
        panic("Something unexpected happened.")
    }()
    wg.Wait()
}

输出量

expected to be called after panic
panic: Something unexpected happened.

goroutine 5 [running]:
main.main.func1(0x416020, 0x0)
    /tmp/sandbox946785562/main.go:17 +0x60
created by main.main
    /tmp/sandbox946785562/main.go:11 +0x80

那么你的第二个问题,“如何防止这种情况?”
如前所述,您可以在panic之后recover
Playground:https://play.golang.org/p/76pPrCVYN8u

package main

import (
    "sync"
    "fmt"
)

func main() {
    wg := &sync.WaitGroup{}
    wg.Add(1)
    go func() {
        defer func(){
            if x:=recover();x!=nil{
                fmt.Printf("%+v\n",x)
            }
            wg.Done()
        }()
        // Long running task
        panic("Something unexpected happened.")
    }()
    wg.Wait()
    for i:=0;i<10;i++{
        fmt.Println(i)
    }
}

输出量

Something unexpected happened.
0
1
2
3
4
5
6
7
8
9
ie3xauqp

ie3xauqp3#

通过添加defer time.Sleep(time.Second)可以引发不需要的行为:
第一个
这个问题的解决方案是不延迟对Done()的调用,而将其放在末尾:

func main() {
    wg := &sync.WaitGroup{}
    wg.Add(1)
    go func() {
        // Long running task which might panic
        wg.Done()
    }()
    wg.Wait()
}

在上面的代码中,没有对go例程行为实现细节的依赖性,并且在出现异常时总是失败。意识到这一点后,我注意到这个问题存在于几乎所有关于这个主题的教程中,这意味着我们正在寻找cargo cult programming的广泛表现。

相关问题