Home > database >  How to stop goRoutine which repeatedly runs based on ticker
How to stop goRoutine which repeatedly runs based on ticker

Time:08-30

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

CodePudding user response:

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

CodePudding user response:

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. 
    // ...
}
  • Related