如何从从WaitGroup调用的函数中捕获运行时错误?

huangapple go评论85阅读模式
英文:

How to catch runtime error from a function invoked from a waitgroup?

问题

如何优雅地处理WaitGroup中的崩溃?

换句话说,在下面的代码片段中,如何捕获调用do()方法的goroutine的panic/崩溃?

func do(){
    str := "abc"
    fmt.Print(str[3])
    defer func() {
        if err := recover(); err != nil {
            fmt.Print(err)
        }
    }()
}

func main() {
    var wg sync.WaitGroup
    
    for i := 0; i < 1; i++ {
        wg.Add(1)
        go do()
        defer func() {
            wg.Done()
            if err := recover(); err != nil {
                fmt.Print(err)
            }
        }()
    }
    wg.Wait()
    fmt.Println("在所有这些调用失败之后,应该打印这一行。")
}
英文:

How to handle crashes in a waitgroup gracefully?

In other words, in the following snippet of code, how to catch the panics/crashes of goroutines invoking method do()?

func do(){
	str := &quot;abc&quot;
	fmt.Print(str[3])
	defer func() {
		if err := recover(); err != nil {
			fmt.Print(err)
		}
	}()
}

func main() {
	var wg sync.WaitGroup
	
	for i := 0; i &lt; 1; i++ {
		wg.Add(1)
		go do()
		defer func() {
			wg.Done()
			if err := recover(); err != nil {
				fmt.Print(err)
			}
		}()
	}
	wg.Wait()
	fmt.Println(&quot;This line should be printed after all those invocations fail.&quot;)
}

答案1

得分: 3

首先,将注册延迟函数以进行恢复的操作放在函数的第一行,因为如果你将其放在最后,它甚至都不会被执行,因为在defer之前的代码/行已经发生了恐慌,所以延迟函数不会被注册,也就无法恢复恐慌状态。

所以将你的do()函数改为以下形式:

func do() {
    defer func() {
        if err := recover(); err != nil {
            fmt.Println("Restored:", err)
        }
    }()
    str := "abc"
    fmt.Print(str[3])
}

其次:仅仅这样修改还不能使你的代码正常工作,因为你在一个延迟函数中调用了wg.Defer(),而这个函数只会在main()函数结束后运行一次,而你在main()函数中调用了wg.Wait(),所以wg.Wait()等待wg.Done()的调用,但是wg.Done()的调用只有在wg.Wait()返回后才会执行,这就造成了死锁。

你应该在do()函数中的延迟函数中调用wg.Done(),像这样:

var wg sync.WaitGroup

func do() {
    defer func() {
        if err := recover(); err != nil {
            fmt.Println(err)
        }
        wg.Done()
    }()
    str := "abc"
    fmt.Print(str[3])
}

func main() {
    for i := 0; i < 1; i++ {
        wg.Add(1)
        go do()
    }
    wg.Wait()
    fmt.Println("This line should be printed after all those invocations fail.")
}

输出结果(在Go Playground上尝试):

Restored: runtime error: index out of range
This line should be printed after all those invocations fail.

当然,这需要将wg变量移到全局范围。另一种选择是将其作为参数传递给do()函数。如果你决定采用这种方式,请注意必须传递一个指向WaitGroup的指针,否则只会传递一个副本(WaitGroup是一个struct类型),在副本上调用WaitGroup.Done()不会对原始对象产生影响。

使用传递WaitGroupdo()的方式:

func do(wg *sync.WaitGroup) {
    defer func() {
        if err := recover(); err != nil {
            fmt.Println("Restored:", err)
        }
        wg.Done()
    }()
    str := "abc"
    fmt.Print(str[3])
}

func main() {
    var wg sync.WaitGroup
    for i := 0; i < 1; i++ {
        wg.Add(1)
        go do(&wg)
    }
    wg.Wait()
    fmt.Println("This line should be printed after all those invocations fail.")
}

输出结果与之前相同。在Go Playground上尝试这个变体。

英文:

First, registering a deferred function to recover should be the first line in the function, as since you do it last, it won't even be reached because the line / code before the defer already panics and so the deferred function does not get registered which would restore the panicing state.

So change your do() function to this:

func do() {
	defer func() {
		if err := recover(); err != nil {
			fmt.Println(&quot;Restored:&quot;, err)
		}
	}()
	str := &quot;abc&quot;
	fmt.Print(str[3])
}

Second: this alone will not make your code work, as you call wg.Defer() in a deferred function which would only run once main() finishes - which is never because you call wg.Wait() in your main(). So wg.Wait() waits for the wg.Done() calls, but wg.Done() calls will not be run until wg.Wait() returnes. It's a deadlock.

You should call wg.Done() from the do() function, in the deferred function, something like this:

var wg sync.WaitGroup

func do() {
	defer func() {
		if err := recover(); err != nil {
			fmt.Println(err)
		}
        wg.Done()
	}()
	str := &quot;abc&quot;
	fmt.Print(str[3])
}

func main() {
	for i := 0; i &lt; 1; i++ {
		wg.Add(1)
		go do()
	}
	wg.Wait()
	fmt.Println(&quot;This line should be printed after all those invocations fail.&quot;)
}

Output (try it on the Go Playground):

Restored: runtime error: index out of range
This line should be printed after all those invocations fail.

This of course needed to move the wg variable to global scope. Another option would be to pass it to do() as an argument. If you decide to go this way, note that you have to pass a pointer to WaitGroup, else only a copy will be passed (WaitGroup is a struct type) and calling WaitGroup.Done() on a copy will not have effect on the original.

With passing WaitGroup to do():

func do(wg *sync.WaitGroup) {
	defer func() {
		if err := recover(); err != nil {
			fmt.Println(&quot;Restored:&quot;, err)
		}
		wg.Done()
	}()
	str := &quot;abc&quot;
	fmt.Print(str[3])
}

func main() {
	var wg sync.WaitGroup
	for i := 0; i &lt; 1; i++ {
		wg.Add(1)
		go do(&amp;wg)
	}
	wg.Wait()
	fmt.Println(&quot;This line should be printed after all those invocations fail.&quot;)
}

Output is the same. Try this variant on the Go Playground.

答案2

得分: 0

@icza做了一个很好的工作,解释了如何正确使用WaitGroup及其函数WaitDone

我喜欢WaitGroup的简洁性。然而,我不喜欢我们需要传递goroutine的引用,因为这意味着并发逻辑会与业务逻辑混合在一起。

所以我想出了这个通用函数来解决这个问题:

// Parallelize并行化函数调用
func Parallelize(functions ...func()) {
var waitGroup sync.WaitGroup
waitGroup.Add(len(functions))

defer waitGroup.Wait()

for _, function := range functions {
    go func(copy func()) {
        defer waitGroup.Done()
        copy()
    }(function)
}

}

所以你的例子可以这样解决:

func do() {
defer func() {
if err := recover(); err != nil {
fmt.Println(err)
}
}()

str := "abc"
fmt.Print(str[3])

}

func main() {
Parallelize(do, do, do)

fmt.Println("这行应该在所有这些调用失败后打印。")

}

如果你想使用它,你可以在这里找到它:https://github.com/shomali11/util

英文:

@icza did a fantastic job explaining how to appropriately use WaitGroup and its functions Wait and Done

I like WaitGroup simplicity. However, I do not like that we need to pass the reference to the goroutine because that would mean that the concurrency logic would be mixed with your business logic.

So I came up with this generic function to solve this problem for me:

// Parallelize parallelizes the function calls
func Parallelize(functions ...func()) {
    var waitGroup sync.WaitGroup
    waitGroup.Add(len(functions))

	defer waitGroup.Wait()

	for _, function := range functions {
    	go func(copy func()) {
	    	defer waitGroup.Done()
		    copy()
	    }(function)
    }
}

So your example could be solved this way:

func do() {
    defer func() {
        if err := recover(); err != nil {
            fmt.Println(err)
        }
    }()

    str := &quot;abc&quot;
    fmt.Print(str[3])
}

func main() {
    Parallelize(do, do, do)

    fmt.Println(&quot;This line should be printed after all those invocations fail.&quot;)
}

If you would like to use it, you can find it here https://github.com/shomali11/util

huangapple
  • 本文由 发表于 2015年12月10日 15:39:18
  • 转载请务必保留本文链接:https://go.coder-hub.com/34196188.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定