Golang,重新启动发生恐慌的例程的正确方法

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

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 (
	&quot;fmt&quot;
	&quot;time&quot;
)

var gVar string
var pCount int

func pinger(c chan int) {
	for i := 0; ; i++ {
		fmt.Println(&quot;adding &quot;, i)
		c &lt;- i
	}
}

func printer(id int, c chan int) {
	defer func() {
		if err := recover(); err != nil {
			fmt.Println(&quot;HERE&quot;, id)
			fmt.Println(err)
			pCount++
			if pCount == 5 {
				panic(&quot;TOO MANY PANICS&quot;)
			} else {
				go printer(id, c)
			}
		}
	}()

	for {
		msg := &lt;-c
		fmt.Println(id, &quot;- ping&quot;, msg, gVar)
		if msg%5 == 0 {
			panic(&quot;PANIC&quot;)
		}

		time.Sleep(time.Second * 1)

	}
}

func main() {
	var c chan int = make(chan int, 2)
	gVar = &quot;Preflight&quot;
	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(&amp;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(&quot;HERE&quot;, id)
			fmt.Println(err)
			if maxPanics == 0 {
				panic(&quot;TOO MANY PANICS&quot;)
			} 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.

Run in Go Playground

package main

import (
	&quot;fmt&quot;
	&quot;time&quot;
)

func main() {
	var pCount int
	ch := make(chan int, 5)

	f := func(i int) {
		defer func() {
			if err := recover(); err != nil {
				ch &lt;- i
			}
		}()

		fmt.Printf(&quot;goroutine f(%v) started\n&quot;, i)
		time.Sleep(1000 * time.Millisecond)
		panic(&quot;goroutine in panic&quot;)
	}

	go f(1)
	go f(2)
	go f(3)
	go f(4)

	for {
		i := &lt;-ch
		pCount++
		if pCount &gt;= 5 {
			fmt.Println(&quot;Too many panics&quot;)
			break
		}
		fmt.Printf(&quot;Detected goroutine f(%v) panic, will restart\n&quot;, 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 = &lt;-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.

huangapple
  • 本文由 发表于 2017年1月12日 08:37:24
  • 转载请务必保留本文链接:https://go.coder-hub.com/41603273.html
匿名

发表评论

匿名网友

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

确定