英文:
Golang, proper way to restart a routine that panicked
问题
我有以下示例代码。我想要保持4个goroutine始终运行。它们有可能发生panic。在发生panic时,我使用recover重新启动goroutine。
我实现的方式是有效的,但我不确定是否是正确和适当的方式。你有什么想法吗?
package main
import (
"fmt"
"time"
)
var gVar string
var pCount int
func pinger(c chan int) {
for i := 0; ; i++ {
fmt.Println("adding ", i)
c <- i
}
}
func printer(id int, c chan int) {
defer func() {
if err := recover(); err != nil {
fmt.Println("HERE", id)
fmt.Println(err)
pCount++
if pCount == 5 {
panic("TOO MANY PANICS")
} else {
go printer(id, c)
}
}
}()
for {
msg := <-c
fmt.Println(id, "- ping", msg, gVar)
if msg%5 == 0 {
panic("PANIC")
}
time.Sleep(time.Second * 1)
}
}
func main() {
var c chan int = make(chan int, 2)
gVar = "Preflight"
pCount = 0
go pinger(c)
go printer(1, c)
go printer(2, c)
go printer(3, c)
go printer(4, c)
var input string
fmt.Scanln(&input)
}
英文:
I have the following sample code. I want to maintain 4 goroutines running at all times. They have the possibility of panicking. In the case of the panic, I have a recover where I restart the goroutine.
The way I implemented works but I am not sure whether its the correct and proper way to do this. Any thoughts
package main
import (
"fmt"
"time"
)
var gVar string
var pCount int
func pinger(c chan int) {
for i := 0; ; i++ {
fmt.Println("adding ", i)
c <- i
}
}
func printer(id int, c chan int) {
defer func() {
if err := recover(); err != nil {
fmt.Println("HERE", id)
fmt.Println(err)
pCount++
if pCount == 5 {
panic("TOO MANY PANICS")
} else {
go printer(id, c)
}
}
}()
for {
msg := <-c
fmt.Println(id, "- ping", msg, gVar)
if msg%5 == 0 {
panic("PANIC")
}
time.Sleep(time.Second * 1)
}
}
func main() {
var c chan int = make(chan int, 2)
gVar = "Preflight"
pCount = 0
go pinger(c)
go printer(1, c)
go printer(2, c)
go printer(3, c)
go printer(4, c)
var input string
fmt.Scanln(&input)
}
答案1
得分: 11
你可以将恢复逻辑提取到一个函数中,例如:
func recoverer(maxPanics, id int, f func()) {
defer func() {
if err := recover(); err != nil {
fmt.Println("HERE", id)
fmt.Println(err)
if maxPanics == 0 {
panic("TOO MANY PANICS")
} else {
go recoverer(maxPanics-1, id, f)
}
}
}()
f()
}
然后像这样使用它:
go recoverer(5, 1, func() { printer(1, c) })
英文:
You can extract the recover logic in a function such as:
func recoverer(maxPanics, id int, f func()) {
defer func() {
if err := recover(); err != nil {
fmt.Println("HERE", id)
fmt.Println(err)
if maxPanics == 0 {
panic("TOO MANY PANICS")
} else {
go recoverer(maxPanics-1, id, f)
}
}
}()
f()
}
And then use it like:
go recoverer(5, 1, func() { printer(1, c) })
答案2
得分: 2
像Zan Lynx的答案一样,我想分享另一种方法来实现它(尽管它与 OP 的方法非常相似)。我使用了一个额外的带缓冲的通道 ch
。当一个 goroutine 发生 panic 时,goroutine 内部的恢复函数会将其标识 i
发送到 ch
中。在 main()
的底部的 for 循环中,它通过从 ch
中接收值来检测哪个 goroutine 处于 panic 状态,并决定是否重新启动。
在 Go Playground 中运行:https://play.golang.org/p/ut1tdtfgrg
package main
import (
"fmt"
"time"
)
func main() {
var pCount int
ch := make(chan int, 5)
f := func(i int) {
defer func() {
if err := recover(); err != nil {
ch <- i
}
}()
fmt.Printf("goroutine f(%v) started\n", i)
time.Sleep(1000 * time.Millisecond)
panic("goroutine in panic")
}
go f(1)
go f(2)
go f(3)
go f(4)
for {
i := <-ch
pCount++
if pCount >= 5 {
fmt.Println("Too many panics")
break
}
fmt.Printf("Detected goroutine f(%v) panic, will restart\n", i)
f(i)
}
}
英文:
Like Zan Lynx's answer, I'd like to share another way to do it (although it's pretty much similar to OP's way.) I used an additional buffered channel ch
. When a goroutine panics, the recovery function inside the goroutine send its identity i
to ch
. In for loop at the bottom of main()
, it detects which goroutine's in panic and whether to restart by receiving values from ch
.
package main
import (
"fmt"
"time"
)
func main() {
var pCount int
ch := make(chan int, 5)
f := func(i int) {
defer func() {
if err := recover(); err != nil {
ch <- i
}
}()
fmt.Printf("goroutine f(%v) started\n", i)
time.Sleep(1000 * time.Millisecond)
panic("goroutine in panic")
}
go f(1)
go f(2)
go f(3)
go f(4)
for {
i := <-ch
pCount++
if pCount >= 5 {
fmt.Println("Too many panics")
break
}
fmt.Printf("Detected goroutine f(%v) panic, will restart\n", i)
f(i)
}
}
答案3
得分: 1
哦,我并不是说以下方法比你的方法更正确。这只是另一种做法。
创建另一个函数,称之为printerRecover
或类似的名称,并在其中使用defer/recover
。然后在printer
函数中循环调用printerRecover
。添加函数返回值来检查是否需要由于某种原因退出goroutine。
英文:
Oh, I am not saying that the following is more correct than your way. It is just another way to do it.
Create another function, call it printerRecover
or something like it, and do your defer / recover in there. Then in printer
just loop on calling printerRecover
. Add in function return values to check if you need the goroutine to exit for some reason.
答案4
得分: 0
你实现的方式是正确的。但对我来说,始终保持运行4个例程的方法看起来不太好,无论是处理例程的ID,还是在延迟中生成例程,都可能由于闭包而导致不可预测的堆栈。我不认为你可以有效地平衡资源。为什么不在需要时简单地生成工作线程呢?
func main() {
...
go func(tasks chan int){ //多路复用器
for {
task := <-tasks //在需要时获取任务
go printer(task) //只是生成处理程序
}
}(ch)
...
}
让运行时自己完成工作如何?这种方式在stdlib的监听器/服务器中已经被使用,并且已知足够高效。生成goroutine非常轻量级,而且运行时非常聪明地平衡负载。当然,你必须以任何方式进行恢复。这是我个人的观点。
英文:
The way you implemented is correct. Just for me the approach to maintain exactly 4 routines running at all times looks not much go_way, either handling routine's ID, either spawning in defer which may leads unpredictable stack due to closure. I don't think you can efficiently balance resource this way. Why don't you like to simple spawn worker when it needed
func main() {
...
go func(tasks chan int){ //multiplexer
for {
task = <-tasks //when needed
go printer(task) //just spawns handler
}
}(ch)
...
}
and let runtime do its job? This way things are done in stdlib listeners/servers and them known to be efficient enough. goroutines are very lightweight to spawn and runtime is quite smart to balance load. Sure you must to recover either way. It is my very personal opinion.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论