如何停止基于ticker重复运行的goRoutine?

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

How to stop goRoutine which repeatedly runs based on ticker

问题

  1. func main() {
  2. // 启动一个长时间运行的进程,捕获 stdout 和 stderr
  3. findCmd := cmd.NewCmd("find", "/", "--name", "needle")
  4. statusChan := findCmd.Start() // 非阻塞
  5. ticker := time.NewTicker(2 * time.Second)
  6. // 每 2 秒打印 stdout 的最后一行
  7. go func() { // 这个 goroutine 在后台一直运行,我该如何停止它?
  8. for range ticker.C {
  9. status := findCmd.Status()
  10. n := len(status.Stdout)
  11. if n > 10 {
  12. findCmd.Stop() // 在这一点上,我想停止这个 goroutine
  13. }
  14. fmt.Println(status.Stdout[n-1])
  15. }
  16. }()
  17. // 1 小时后停止命令
  18. go func() {
  19. <-time.After(1 * time.Hour)
  20. findCmd.Stop()
  21. }()
  22. // 检查命令是否完成
  23. select {
  24. case finalStatus := <-statusChan:
  25. // 完成
  26. default:
  27. // 否,仍在运行
  28. }
  29. // 阻塞等待命令退出、停止或被杀死
  30. finalStatus := <-statusChan
  31. }

使用 goroutine 来每 2 秒打印 stdout 的最后一行,尝试了几种方法来停止 goroutine,但都没有成功。如何使用通道来停止这个 goroutine?

英文:
  1. func main() {
  2. // Start a long-running process, capture stdout and stderr
  3. findCmd := cmd.NewCmd(&quot;find&quot;, &quot;/&quot;, &quot;--name&quot;, &quot;needle&quot;)
  4. statusChan := findCmd.Start() // non-blocking
  5. ticker := time.NewTicker(2 * time.Second)
  6. // Print last line of stdout every 2s
  7. go func() { ------------ this keeps running in background, how do I stop this goroutine
  8. for range ticker.C {
  9. status := findCmd.Status()
  10. n := len(status.Stdout)
  11. if len &gt; 10 {
  12. findCmd.Stop() ------- at this point I want to stop this goroutine
  13. }
  14. fmt.Println(status.Stdout[n-1])
  15. }
  16. }()
  17. // Stop command after 1 hour
  18. go func() {
  19. &lt;-time.After(1 * time.Hour)
  20. findCmd.Stop()
  21. }()
  22. // Check if command is done
  23. select {
  24. case finalStatus := &lt;-statusChan:
  25. // done
  26. default:
  27. // no, still running
  28. }
  29. // Block waiting for command to exit, be stopped, or be killed
  30. finalStatus := &lt;-statusChan
  31. }

Goroutine is used to print last line of stdout every 2s, tried few things to stop the goroutine but none worked.
How can I stop that goroutine using channels

答案1

得分: 2

这是使用select和case的示例。你可以像这样做:

  1. ticker := time.NewTicker(2 * time.Second)
  2. done := make(chan struct{})
  3. // 当需要停止的goroutine
  4. go func() {
  5. select {
  6. case <-ticker.C:
  7. // 做一些事情
  8. case <-done:
  9. ticker.Stop() // 根据你的目的,这是可选的
  10. }
  11. }()
  12. // 做一些事情
  13. // 停止上面的goroutine
  14. close(done) // 当有多个你想要停止的goroutine时,读取已关闭的通道会返回通道的零值
  15. // 或者
  16. done <- struct{}{} // 当只有一个你想要停止的goroutine时
英文:

This is where select, case is used. You can do something like this:

  1. ticker := time.NewTicker(2 * time.Second)
  2. done := make(chan struct{})
  3. // The go routine that you want to stop when needed
  4. go func() {
  5. select {
  6. case &lt;-ticker.C:
  7. // do stuff
  8. case &lt;-done:
  9. ticker.Stop() // this is optional based on your purpose
  10. }
  11. }()
  12. // do stuff
  13. // stop above go routine
  14. close(done) // when there are many go routine you want to stop as reading from closed channel return the channel zero value
  15. // OR
  16. done &lt;- struct{}{} // when there is only 1 go routine you want to stop

答案2

得分: 0

其中一个选项是将上下文传递给这样的函数/特性。每当你传递一个上下文,你可以捕捉到取消信号,并在选择语句中使用它。

  1. func main() {
  2. appCtx := context.Background()
  3. // ...
  4. ticker := time.NewTicker(2 * time.Second)
  5. tickerCtx, tickerCancel := context.WithCancel(appCtx) // 为ticker创建上下文,派生自根上下文
  6. // 每2秒打印stdout的最后一行
  7. go func(ctx context.Context) {
  8. for {
  9. select {
  10. case <-ticker.C: // tick!
  11. // ...
  12. // 如果你需要执行任何需要上下文的操作,请传递`ctx`。
  13. // 如果上下文被取消,所有操作也应该被终止。
  14. case <-ctx.Done() // 上下文已取消
  15. return
  16. }
  17. }
  18. }(tickerCtx)
  19. // 1小时后停止命令
  20. go func() {
  21. <-time.After(1 * time.Hour)
  22. tickerCancel()
  23. }()
  24. // ...
  25. }

所以,通过这种方式我们使用了上下文,并且我们对所有需要上下文的操作都有控制权(我们可以随时取消它们)。

在你的情况下,还可以进行另一种改进,即在一段时间后终止基于上下文的操作(无需使用另一个带有定时器的goroutine):

  1. func main() {
  2. appCtx := context.Background()
  3. // ...
  4. ticker := time.NewTicker(2 * time.Second)
  5. tickerCtx, tickerCancel := context.WithTimeout(appCtx, time.Duration(1) * time.Hour) // 创建带有超时的上下文
  6. // 每2秒打印stdout的最后一行
  7. go func(ctx context.Context) {
  8. for {
  9. select {
  10. case <-ticker.C: // tick!
  11. // ...
  12. case <-ctx.Done() // 上下文已取消
  13. return
  14. }
  15. }
  16. }(tickerCtx)
  17. // 我们不需要运行一个新的goroutine来终止上下文。我们设置的超时会完成这个工作。
  18. // ...
  19. }
英文:

One of the options is to pass the context to such a function/feature. Whenever you pass a context you can catch the cancel signal and use it in the select statements.

  1. func main() {
  2. appCtx := context.Background()
  3. // ...
  4. ticker := time.NewTicker(2 * time.Second)
  5. tickerCtx, tickerCancel := context.WithCancel(appCtx) // create context for ticker, derive from root context
  6. // Print last line of stdout every 2s
  7. go func(ctx context.Context) {
  8. for {
  9. select {
  10. case &lt;-ticker.C: // tick!
  11. // ...
  12. // if you need to perform any operation requiring the context pass `ctx`.
  13. // If case the ctx is canceled all operations should be terminated as well.
  14. case &lt;-ctx.Done() // context has been cancelled
  15. return
  16. }
  17. }
  18. }(tickerCtx)
  19. // Stop command after 1 hour
  20. go func() {
  21. &lt;-time.After(1 * time.Hour)
  22. tickerCancel()
  23. }()
  24. // ...
  25. }

So, this way we are using context, and we have control over all operations where a context is needed (we can cancel them at any time).

In your case there could be another improvement, to terminate the ctx-based operation after some time (without the need to use another goroutine with a timer):

  1. func main() {
  2. appCtx := context.Background()
  3. // ...
  4. ticker := time.NewTicker(2 * time.Second)
  5. tickerCtx, tickerCancel := context.WithTimeout(appCtx, time.Duration(1) * time.Hour) // create a context with a timeout
  6. // Print last line of stdout every 2s
  7. go func(ctx context.Context) {
  8. for {
  9. select {
  10. case &lt;-ticker.C: // tick!
  11. // ...
  12. case &lt;-ctx.Done() // context has been cancelled
  13. return
  14. }
  15. }
  16. }(tickerCtx)
  17. // We don&#39;t need to run a new goroutine terminate the ctx. The timeout we set will do the job.
  18. // ...
  19. }

huangapple
  • 本文由 发表于 2022年8月26日 19:30:28
  • 转载请务必保留本文链接:https://go.coder-hub.com/73500365.html
匿名

发表评论

匿名网友

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

确定