2

I have a question similar to How to stop a goroutine with a small twist. I don't know for sure if the goroutine is running.

var quit = make(chan bool)

func f1() {
    go func() {
        t := time.NewTimer(time.Minute)
        select {
        case <-t.C:
            // Do stuff
        case <-quit:
            return
        }
    }()
}

func f2() {
    quit <- true
}

If f2() is called less than a minute after f1(), then the goroutine returns. However, if it is called later than one minute, the goroutine will have already returned and f2() would block. I want f2() to cancel the goroutine if it is running and do nothing otherwise.

What I'm trying to achieve here is to perform a task if and only if it is not canceled within a minute of creation.

Clarifications:

  • There is nothing to stop f2() from being called more than once.
  • There is only one goroutine running at a time. The caller of f1() will make sure that it's not called more than once per minute.
diaa
  • 316
  • 2
  • 15

2 Answers2

9

Use contexts.

Run f1 with the context which may be cancelled. Run f2 with the associated cancellation function.

func f1(ctx context.Context) {
    go func(ctx context.Context) {
        t := time.NewTimer(time.Minute)
        select {
        case <-t.C:
            // Do stuff
        case <-ctx.Done():
            return
        }
    }(ctx)
}

func f2(cancel context.CancelFunc) {
    cancel()
}

And later, to coordinate the two functions, you would do this:

    ctx, cancel := context.WithCancel(context.Background())
    f1(ctx)
    f2(cancel)

You can also experiment with the context.WithTimeout function to incorporate externally-defined timeouts.

In the case where you don't know whether there is a goroutine running already, you can initialize the ctx and cancel variables like above, but don't pass them into anything. This avoids having to check for nil.

Remember to treat ctx and cancel as variables to be copied, not as references, because you don't want multiple goroutines to share memory - that may cause a race condition.

Tyler Kropp
  • 561
  • 3
  • 13
-1

You can give the channel a buffer size of 1. This means you can send one value to it without blocking, even if that value is not received right away (or at all).

var quit = make(chan bool, 1)

I think the top answer is better, this is just another solution that could translate to other situations.

Hymns For Disco
  • 7,530
  • 2
  • 17
  • 33
  • That is true, but the next goroutine will quit immediately as the channel already has an element. – diaa Apr 08 '21 at 17:36
  • 3
    Yes. The same problem also applies to the `close(quit)` method. The channel will be closed forever, and any subsequent reads will behave the same. If you need to quit each goroutine individually, they should not share a `quit` channel. And if you want to quit them all at the same time and forever, you should use `close(quit)`. – Hymns For Disco Apr 08 '21 at 17:44
  • I probably didn't explain the issue properly. There will be only one goroutine active at a time and that's why there is only one channel. But there will be subsequent calls to `f1()` (after the existing goroutine is done) that would create a new instance. – diaa Apr 08 '21 at 17:48
  • It's a bad idea to reuse quit channels, as it leads to exactly the kinds of problems you're having right now. There is no clean way to "reset" a channel. – Hymns For Disco Apr 08 '21 at 18:02
  • You're probably right. Not sure how to solve this otherwise though. Thanks for the answer anyway :) – diaa Apr 08 '21 at 18:05