英文:
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
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论