英文:
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("find", "/", "--name", "needle")
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 > 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() {
<-time.After(1 * time.Hour)
findCmd.Stop()
}()
// Check if command is done
select {
case finalStatus := <-statusChan:
// done
default:
// no, still running
}
// Block waiting for command to exit, be stopped, or be killed
finalStatus := <-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 <-ticker.C:
// do stuff
case <-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 <- 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 <-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 <-ctx.Done() // context has been cancelled
return
}
}
}(tickerCtx)
// Stop command after 1 hour
go func() {
<-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 <-ticker.C: // tick!
// ...
case <-ctx.Done() // context has been cancelled
return
}
}
}(tickerCtx)
// We don't need to run a new goroutine terminate the ctx. The timeout we set will do the job.
// ...
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论