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

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

How to stop goRoutine which repeatedly runs based on ticker

问题

func main() {
	// 启动一个长时间运行的进程,捕获 stdout 和 stderr
	findCmd := cmd.NewCmd("find", "/", "--name", "needle")
	statusChan := findCmd.Start() // 非阻塞

	ticker := time.NewTicker(2 * time.Second)

	// 每 2 秒打印 stdout 的最后一行
	go func() { // 这个 goroutine 在后台一直运行,我该如何停止它?
		for range ticker.C {
			status := findCmd.Status()
			n := len(status.Stdout)
			if n > 10 {
				findCmd.Stop() // 在这一点上,我想停止这个 goroutine
			}
			fmt.Println(status.Stdout[n-1])
		}
	}()

	// 1 小时后停止命令
	go func() {
		<-time.After(1 * time.Hour)
		findCmd.Stop()
	}()

	// 检查命令是否完成
	select {
	case finalStatus := <-statusChan:
		// 完成
	default:
		// 否,仍在运行
	}

	// 阻塞等待命令退出、停止或被杀死
	finalStatus := <-statusChan
}

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

英文:
func main() {
	// Start a long-running process, capture stdout and stderr
	findCmd := cmd.NewCmd(&quot;find&quot;, &quot;/&quot;, &quot;--name&quot;, &quot;needle&quot;)
	statusChan := findCmd.Start() // non-blocking

	ticker := time.NewTicker(2 * time.Second)

	// Print last line of stdout every 2s
	go func() {               ------------ this keeps running in background, how do I stop this goroutine
		for range ticker.C {
			status := findCmd.Status()
			n := len(status.Stdout)
            if len &gt; 10 {
               findCmd.Stop()     ------- at this point I want to stop this goroutine
            }
			fmt.Println(status.Stdout[n-1])
		}
	}()

	// Stop command after 1 hour
	go func() {
		&lt;-time.After(1 * time.Hour)
		findCmd.Stop()
	}()

	// Check if command is done
	select {
	case finalStatus := &lt;-statusChan:
		// done
	default:
		// no, still running
	}

	// Block waiting for command to exit, be stopped, or be killed
	finalStatus := &lt;-statusChan
}

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的示例。你可以像这样做:

ticker := time.NewTicker(2 * time.Second)

done := make(chan struct{})
// 当需要停止的goroutine
go func() {
	select {
	case <-ticker.C:
		// 做一些事情
	case <-done:
		ticker.Stop() // 根据你的目的,这是可选的
	}
}()

// 做一些事情

// 停止上面的goroutine
close(done) // 当有多个你想要停止的goroutine时,读取已关闭的通道会返回通道的零值
// 或者
done <- struct{}{}  // 当只有一个你想要停止的goroutine时
英文:

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

ticker := time.NewTicker(2 * time.Second)

done := make(chan struct{})
// The go routine that you want to stop when needed
go func() {
	select {
	case &lt;-ticker.C:
		// do stuff
	case &lt;-done:
		ticker.Stop() // this is optional based on your purpose
	}
}()

// do stuff

// stop above go routine
close(done) // when there are many go routine you want to stop as reading from closed channel return the channel zero value
// OR
done &lt;- struct{}{}  // when there is only 1 go routine you want to stop

答案2

得分: 0

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

func main() {
    appCtx := context.Background()

    // ...
    
    ticker := time.NewTicker(2 * time.Second)
    tickerCtx, tickerCancel := context.WithCancel(appCtx) // 为ticker创建上下文,派生自根上下文

    // 每2秒打印stdout的最后一行
    go func(ctx context.Context) {
        for {
            select {
            case <-ticker.C: // tick!
                // ...
                // 如果你需要执行任何需要上下文的操作,请传递`ctx`。
                // 如果上下文被取消,所有操作也应该被终止。
                
            case <-ctx.Done() // 上下文已取消
                return
            }
        }
    }(tickerCtx)

    // 1小时后停止命令
    go func() {
        <-time.After(1 * time.Hour)
        tickerCancel()
    }() 
    // ...
}

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

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

func main() {
    appCtx := context.Background()

    // ...
    
    ticker := time.NewTicker(2 * time.Second)
    tickerCtx, tickerCancel := context.WithTimeout(appCtx, time.Duration(1) * time.Hour) // 创建带有超时的上下文

    // 每2秒打印stdout的最后一行
    go func(ctx context.Context) {
        for {
            select {
            case <-ticker.C: // tick!
                // ...
            case <-ctx.Done() // 上下文已取消
                return
            }
        }
    }(tickerCtx)

    // 我们不需要运行一个新的goroutine来终止上下文。我们设置的超时会完成这个工作。
    // ...
}
英文:

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.

func main() {
    appCtx := context.Background()

    // ...
    
    ticker := time.NewTicker(2 * time.Second)
    tickerCtx, tickerCancel := context.WithCancel(appCtx) // create context for ticker, derive from root context

    // Print last line of stdout every 2s
    go func(ctx context.Context) {
        for {
            select {
            case &lt;-ticker.C: // tick!
                // ...
                // if you need to perform any operation requiring the context pass `ctx`.
                // If case the ctx is canceled all operations should be terminated as well.
                
            case &lt;-ctx.Done() // context has been cancelled
                return
            }
        }
    }(tickerCtx)

    // Stop command after 1 hour
    go func() {
        &lt;-time.After(1 * time.Hour)
        tickerCancel()
    }() 
    // ...
}

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):

func main() {
    appCtx := context.Background()

    // ...
    
    ticker := time.NewTicker(2 * time.Second)
    tickerCtx, tickerCancel := context.WithTimeout(appCtx, time.Duration(1) * time.Hour) // create a context with a timeout

    // Print last line of stdout every 2s
    go func(ctx context.Context) {
        for {
            select {
            case &lt;-ticker.C: // tick!
                // ...
            case &lt;-ctx.Done() // context has been cancelled
                return
            }
        }
    }(tickerCtx)

    // We don&#39;t need to run a new goroutine terminate the ctx. The timeout we set will do the job. 
    // ...
}

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:

确定