0

Please see the following example.

Playground link

type runner struct{}

func (r *runner) StopProcessing() {
    // how to stop?
}

func (r *runner) StartProcessing() {

    go func() {
        for {
            fmt.Println("doing stuff")
            time.Sleep(1 * time.Second)
        }
    }()

}

As you can see I have a struct which does stuff, it's "running". It starts running when I call the run.StartProcessing() method. It then fires an endless running for{}-loop in a goroutine. Great, but I also want to be able to stop this process. And I really don't know how to achieve this. Any help is highly appreciated.

Flimzy
  • 68,325
  • 15
  • 126
  • 165
Rogier Lommers
  • 2,103
  • 3
  • 22
  • 36
  • 2
    Linking to code helps the community less. The playground links are nice, but the core important code should be placed in the question. – RayfenWindspear Nov 29 '18 at 21:12
  • 1
    Possible duplicate of [how to stop a goroutine](https://stackoverflow.com/questions/6807590/how-to-stop-a-goroutine) – David Maze Nov 29 '18 at 23:41

3 Answers3

2

You can use a context to get timeouts and cancelation, without requiring any extra API.

type runner struct{}

func (r *runner) StartProcessing(ctx context.Context) {
    go func() {
        for {
            select {
            case <-ctx.Done():
                return
            default:
            }
            fmt.Println("doing stuff")
            time.Sleep(1 * time.Second)
        }
    }()
}

This gives you the flexibility to set a timeout, or cancel it at any time. You can also make use of existing contexts which may want to timeout or cancel sooner without your knowledge.

// normal timeout after 10 seconds
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
run.StartProcessing(ctx)

// decide to cancel early
time.Sleep(3 * time.Second)
cancel()
JimB
  • 96,249
  • 11
  • 237
  • 229
-1

By using a channel to signal when to break out of a goroutine. The relevant part of your code would look something like this

type runner struct {
    stop chan bool
}

func (r *runner) StopProcessing() {
    r.stop <- true
}

func (r *runner) StartProcessing() {
    r.stop = make(chan bool)
    go func() {
        for {
            fmt.Println("doing stuff")
            time.Sleep(1 * time.Second)
            select {
            case _ = <-r.stop:
                close(r.stop)
                return
            default:
            }
        }
    }()
}

You can see a full example here https://play.golang.org/p/OUn18Cprs0I

Mad Wombat
  • 13,524
  • 13
  • 64
  • 103
  • Beat me to it. Probably best to close the channel before the break. – RayfenWindspear Nov 29 '18 at 21:21
  • Agreed. Updated the code for both the answer and playground. Also, I think returning out of the function is more definite than breaking out of the loop – Mad Wombat Nov 29 '18 at 21:23
  • That was how I did it when I was about to post my version. – RayfenWindspear Nov 29 '18 at 21:24
  • The channel here does not need to be closed, nor should the receiver be responsible for closing it. – JimB Nov 29 '18 at 21:56
  • Why not? You need to make an actual argument if you want to convince people. StartProcessing() creates the channel and StopProcessing() closes the channel, so if StartProcessing() is called again a new channel will be created. – Mad Wombat Nov 29 '18 at 22:04
  • 1
    @MadWombat: closing a channel is not a cleanup action, it's a type of message. The advice to not have the receiver close a channel is generally sounds advice to avoid sending on a closed channel. – JimB Nov 29 '18 at 22:13
  • This channel is only used to start/stop the processing routine. It is created on start and closed on stop and that is the only place where it is ever used. If this was a channel used to transfer actual data between multiple goroutines, I would agree with you. – Mad Wombat Nov 29 '18 at 22:15
  • My comment was only for code quality, as it would not pass our code review. Do with it what you will. – JimB Nov 29 '18 at 22:18
-1

You might try something like this... You might not need atomic but this works.

package main

import (
    "fmt"
    "time"
    "sync/atomic"
)

var trip = int64(0)

type runner struct{}

func (r *runner) StopProcessing() {
    atomic.AddInt64(&trip, 1)
}

func (r *runner) StartProcessing() {
    go func() {
        for {
            if trip > 0 {
                break
            }
            fmt.Println("doing stuff")
            time.Sleep(1 * time.Second)
        }
    }()
}

func newRunner() *runner {
    return &runner{}
}

func main() {
    run := newRunner()
    run.StartProcessing() 

    // now wait 4 seconds and the kill the process
    time.Sleep(4 * time.Second)
    run.StopProcessing()
}
cheznic
  • 9
  • 3
  • Using global variables to keep temporary state is generally frowned upon – Mad Wombat Nov 29 '18 at 22:08
  • Also, using int64 where its value will ever be 0 or 1 is not a good thing either. Although you can call StopProcessing() repeatedly and it will keep incrementing. Also, since there is a single goroutine running, no need to use atomic updates. – Mad Wombat Nov 29 '18 at 22:10
  • 1
    You cannot read a value concurrently, even with an atomic write. You must safely load the value, or better yet, use the builtin concurrency features of the language, like a channel. – JimB Nov 29 '18 at 22:10