英文:
pass recovery to upper goroutine golang
问题
我创建了一个示例,在其中并发运行函数,函数内部发生 panic 和 recover:
package main
import "fmt"
func main() {
// "main" recovery
defer func() {
if r := recover(); r != nil {
fmt.Println("main goroutine panicked:", r)
}
}()
// 在其中并发运行函数,函数内部发生 panic
chanStr := make(chan string)
go func() {
// "internal" goroutine recovery
defer func() {
if r := recover(); r != nil {
fmt.Println("internal goroutine panicked:", r)
}
chanStr <- "hello world"
}()
// 在 "internal" recovery 和 "main" recovery 中都发生 panic,并希望进行恢复
panic("NOT main goroutine")
}()
// 等待带有 "internal" goroutine 的 chan 发生 panic 和恢复
str := <-chanStr
fmt.Println(str)
// panic("main")
}
它的输出是:
internal goroutine panicked: NOT main goroutine
hello world
是否有可能修改我的代码,使恢复从 "internal" 传递到 "main"?换句话说,我希望它在控制台上输出:
internal goroutine panicked: NOT main goroutine
main goroutine panicked: main
hello world
我尝试通过完全删除 "internal" recovery 函数来实现这一点,但在这种情况下,"main" recovery 无法恢复 "internal" goroutine 中的 panic。
更新
我尝试按照 @Momer 的建议,通过通道发送错误并在主 goroutine 中处理它,而不是试图将 panic 向上传递:
package main
import (
"errors"
"fmt"
)
func main() {
// "main" recovery
defer func() {
if r := recover(); r != nil {
fmt.Println("main goroutine panicked:", r)
}
}()
// 在其中并发运行函数,函数内部发生 panic
chanStr := make(chan string)
chanErr := make(chan error)
var err error
go func() {
// "internal" goroutine recovery
defer func() {
if r := recover(); r != nil {
fmt.Println("internal goroutine panicked:", r)
switch t := r.(type) {
case string:
fmt.Println("err is string")
err = errors.New(t)
case error:
fmt.Println("err is error")
err = t
default:
fmt.Println("err is unknown")
err = errors.New("Unknown error")
}
chanErr <- err
chanStr <- ""
}
}()
// 在 "internal" recovery 和 "main" recovery 中都发生 panic,并希望进行恢复
panic("NOT main goroutine")
chanStr <- "hello world"
chanErr <- nil
}()
// 等待带有 "internal" goroutine 的 chan 发生 panic 和恢复
str := <-chanStr
err = <-chanErr
fmt.Println(str)
fmt.Println(err)
// panic("main")
}
它给出了错误:
all goroutines are asleep - deadlock
完整输出:
go run /goPath/parentRecoverty2.go
internal goroutine panicked: NOT main goroutine
err is string
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan receive]:
main.main()
/goPath/parentRecoverty2.go:48 +0x1d4
goroutine 5 [chan send]:
main.func·002()
/goPath/parentRecoverty2.go:37 +0x407
main.func·003()
/goPath/parentRecoverty2.go:42 +0x130
created by main.main
/goPath/parentRecoverty2.go:46 +0x190
exit status 2
英文:
I created an example where I run function concurrently inside which I panic and recover:
package main
import "fmt"
func main() {
// "main" recovery
defer func() {
if r := recover(); r != nil {
fmt.Println("main goroutine paniced:", r)
}
}()
// running function concurrently inside which I panic
chanStr := make(chan string)
go func() {
// this "internal" goroutin recovery
defer func() {
if r := recover(); r != nil {
fmt.Println("internal goroutine paniced:", r)
}
chanStr <- "hello world"
}()
// panicking and wanting recovery not only in "internal" recovery but in "main" recovery as well
panic("NOT main goroutine")
}()
// waiting for chan with "internal" goroutine panicking and recovery
str := <-chanStr
fmt.Println(str)
// panic("main")
}
It gives output:
internal goroutine panicked: NOT main goroutine
hello world
Is it possible to change my code to make pass recovery from "internal" to "main"? In other words I want it to write down to console:
internal goroutine paniced: NOT main goroutine
main goroutine paniced: main
hello world
I tried to implement this by removing "internal" recovery func at all, but "main" recovery do not recover panic inside "internal" goroutine in this case.
Update
I tried to follow @Momer's advice and send an error through the channel and handle it in the main goroutine, instead of trying to bubble the panic up:
package main
import (
"errors"
"fmt"
)
func main() {
// "main" recovery
defer func() {
if r := recover(); r != nil {
fmt.Println("main goroutine paniced:", r)
}
}()
// running func concarantly inside which I panic
chanStr := make(chan string)
chanErr := make(chan error)
var err error
go func() {
// this "internal" goroutin recovery
defer func() {
if r := recover(); r != nil {
fmt.Println("internal goroutine paniced:", r)
switch t := r.(type) {
case string:
fmt.Println("err is string")
err = errors.New(t)
case error:
fmt.Println("err is error")
err = t
default:
fmt.Println("err is unknown")
err = errors.New("Unknown error")
}
chanErr <- err
chanStr <- ""
}
}()
// panicing and wanting recovery not only in "internal" recovery but in "main" recovery as well
panic("NOT main goroutine")
chanStr <- "hello world"
chanErr <- nil
}()
// waiting for chan with "internal" goroutin panicing and recovery
str := <-chanStr
err = <-chanErr
fmt.Println(str)
fmt.Println(err)
// panic("main")
}
It gives error
all goroutines are asleep - deadlock
Full output:
go run /goPath/parentRecoverty2.go
internal goroutine paniced: NOT main goroutine
err is string
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan receive]:
main.main()
/goPath/parentRecoverty2.go:48 +0x1d4
goroutine 5 [chan send]:
main.func·002()
/goPath/parentRecoverty2.go:37 +0x407
main.func·003()
/goPath/parentRecoverty2.go:42 +0x130
created by main.main
/goPath/parentRecoverty2.go:46 +0x190
exit status 2
答案1
得分: 2
我在Go语言中使用panic/recover,类似于Java或C++中的try/catch/final块。
更多详细信息,请访问Handling panics (from Golang spec)。
因此,你可以将panic传递给方法的调用者。下面是一个简单的代码示例,希望对你有所帮助:
注意:在函数Foo()中,我使用recover()来捕获出现问题的情况,然后重新引发panic,以便在外部调用者中捕获它。
package main
import (
"fmt"
"time"
)
func Foo() {
defer func() {
if x := recover(); x != nil {
fmt.Printf("Runtime panic: %v \n", x)
panic("Ah oh ... Panic in defer")
}
}()
panic("Panic in Foo() !")
}
func Game() {
defer func(){
fmt.Println("Clean up in Game()")
}()
defer func() {
if x := recover(); x != nil {
fmt.Println("Catch recover panic !!! In Game()")
}
}()
Foo()
}
func main() {
defer func() {
fmt.Println("Program Quit ... ")
}()
fmt.Println("-----------Split-------------")
go Game()
time.Sleep(1 * time.Millisecond)
fmt.Println("-----------Split-------------")
}
希望对你有所帮助!
英文:
I took panic/recover in golang as try/catch/final blocks in java or c++.
For more detail, you can visit Handling panics (from Golang spec).
so you can pass a panic to method's caller.
a simple code is below, hope it helps
Note: in function Foo(), I use recover() to catch things going wrong, and then re-panic in order to catch it later in outer caller.
package main
import (
"fmt"
"time"
)
func Foo() {
defer func() {
if x := recover(); x != nil {
fmt.Printf("Runtime panic: %v \n", x)
panic("Ah oh ... Panic in defer")
}
}()
panic("Panic in Foo() !")
}
func Game() {
defer func(){
fmt.Println("Clean up in Game()")
}()
defer func() {
if x := recover(); x != nil {
fmt.Println("Catch recover panic !!! In Game()")
}
}()
Foo()
}
func main() {
defer func() {
fmt.Println("Program Quit ... ")
}()
fmt.Println("-----------Split-------------")
go Game()
time.Sleep(1 * time.Millisecond)
fmt.Println("-----------Split-------------")
}
答案2
得分: 1
在你更新的问题中,一个线程被阻塞在从chanStr读取,而另一个线程被阻塞在向chanErr写入。
交换写入的顺序应该可以解决死锁问题。
defer func() {
if r := recover(); r != nil {
fmt.Println("内部协程发生恐慌:", r)
switch t := r.(type) {
case string:
fmt.Println("错误是字符串")
err = errors.New(t)
case error:
fmt.Println("错误是错误类型")
err = t
default:
fmt.Println("错误是未知类型")
err = errors.New("未知错误")
}
chanStr <- ""
chanErr <- err
}
}
英文:
In your updated question, one thread is blocked by reading from chanStr while the other thread is blocked by writing into chanErr.
Switching the order of writes should resolve the deadlock.
defer func() {
if r := recover(); r != nil {
fmt.Println("internal goroutine paniced:", r)
switch t := r.(type) {
case string:
fmt.Println("err is string")
err = errors.New(t)
case error:
fmt.Println("err is error")
err = t
default:
fmt.Println("err is unknown")
err = errors.New("Unknown error")
}
chanStr <- ""
chanErr <- err
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论