Search code examples
goconcurrencychannelgoroutine

How to pause and resume goroutine?


I am trying to pause and resume groutine. I understand I can sleep the run, but I am looking for is like a button "pause/resume" rather than a timer.

Here is my attempt. I am using the blocking feature of channel to pause, and select to switch what to execute based on channel value. However, the output is always Running in my case.

func main() {
    ctx := wctx{}
    go func(ctx wctx) {
        for {
            time.Sleep(1 * time.Second)
            select {
            case <-ctx.pause:
                fmt.Print("Paused")
                <-ctx.pause
            case <-ctx.resume:
                fmt.Print("Resumed")
            default:
                fmt.Print("Running \n")
            }
        }
    }(ctx)

    ctx.pause <- struct{}{}
    ctx.resume <- struct{}{}
}

type wctx struct {
    pause  chan struct{}
    resume chan struct{}
}

Solution

  • A select with multiple ready cases chooses one pseudo-randomly. So if the goroutine is "slow" to check those channels, you might send a value on both pause and resume (assuming they are buffered) so receiving from both channels could be ready, and resume could be chosen first, and in a later iteration the pause when the goroutine should not be paused anymore.

    For this you should use a "state" variable synchronized by a mutex. Something like this:

    const (
        StateRunning = iota
        StatePaused
    )
    
    type wctx struct {
        mu    sync.Mutex
        state int
    }
    
    func (w *wctx) SetState(state int) {
        w.mu.Lock()
        defer w.mu.Unlock()
        w.state = state
    }
    
    func (w *wctx) State() int {
        w.mu.Lock()
        defer w.mu.Unlock()
        return w.state
    }
    

    Testing it:

    ctx := &wctx{}
    go func(ctx *wctx) {
        for {
            time.Sleep(1 * time.Millisecond)
            switch state := ctx.State(); state {
            case StatePaused:
                fmt.Println("Paused")
            default:
                fmt.Println("Running")
            }
        }
    }(ctx)
    
    time.Sleep(3 * time.Millisecond)
    ctx.SetState(StatePaused)
    time.Sleep(3 * time.Millisecond)
    ctx.SetState(StateRunning)
    time.Sleep(2 * time.Millisecond)
    

    Output (try it on the Go Playground):

    Running
    Running
    Running
    Paused
    Paused
    Paused
    Running
    Running