4

I have a goroutine that calls a function and with a special parameter i want to start or stop this goroutine. My problem is that this code never stops my goroutine, it creates everytime a new job.

quit := make(chan bool)
run := make(chan bool)

    go func() {
        for {
            select {
            case <-quit:
                close(run)
            case <-run:
                myFunc(c)
            default:
            }
        }
    }()

    if x == true {
        quit <- true
    } else {
        run <- true
    }

How do I stop my routine?

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189

3 Answers3

1

When you close the run channel, case <-run will always trigger: listening on a closed channel returns a zero value immediately.

if you want to stop the goroutine, you should return after you get the <-quit signal.

As a side note, your default: clause makes the for loop actively work, you should get rid of it (you will still be listening on both channels)

Dean Elbaz
  • 2,310
  • 17
  • 17
1

Here's an isolated commented runable version of how such a signaling system might be implemented.

package main

import (
    "time"
    "log"
)

func main() {
    statusChannel := make(chan bool)
    go applicationLoop(statusChannel)

    // reasonably random outcome for testing
    if time.Now().Unix() % 2 == 0 {
        statusChannel<-true
    } else {
        statusChannel<-false
    }

    for {
        // busy loop for testing
        time.Sleep(1000)
    }
}

func applicationLoop(statusChannel chan bool) {
    defer close(statusChannel)
    for {
        log.Printf("waiting for signal...\n")
        shouldContinue := <-statusChannel
        if !shouldContinue {
            log.Print("received false, breaking...\n")
            break
        }
        // run your code here
        // you should use a second channel to return results, as the channel is not buffered
        log.Print("working...\n")
    }
}

Do note that sending a value to statusChannel while is is not listening for a value will make the example blow up in your face. Either use a buffered channel or a channel that signals back to main when the goroutine is back to listening for a signal.

Aurelia
  • 1,052
  • 6
  • 28
  • That's it! It runs, thank you! –  Feb 28 '17 at 20:11
  • No reason to do a read from the channel and then check the value. You can read from the channel directly in the if statement: `if !<-statusChannel {`. Example: https://play.golang.org/p/3q5jpC19rW. You can also compress the send on the status channel by simply unconditionally sending the results of boolean expression you're currently using as the if statement's condition. – Kaedys Feb 28 '17 at 20:13
1

This problem has two parts.

First we need to stop child goroutines somehow in a way that even if a parent goroutines stops, all it's children should get notified and stop - a hierarchy of stop signals that goes down but not up.

On the other hand the parent needs to wait for it's children until they are done. Otherwise we would return from a goroutine or even exit from the app before some goroutines are finished properly.

For simplicity we ignore implementing error handling, timeouts and the like.

For handling the first problem we use context.Context which gives us a nice hierarchy of execution context handling tools and for solving the second problem we use sync.WaitGroup which allows us to wait for a group of goroutines to complete their tasks. A simple demonstration would be:

func main() {
    all := &sync.WaitGroup{}
    rootCtx, rootCancel := context.WithCancel(context.Background())

    all.Add(1)
    go level1(rootCtx, all)

    // just to simulate stop, we could use an os signal instead
    // app ends after 3 seconds
    go func() {
        time.Sleep(time.Second * 3)
        rootCancel()
    }()

    all.Wait()
}

func level1(parent context.Context, all *sync.WaitGroup) {
    defer all.Done()
    l1Ctx, l1Cancel := context.WithCancel(parent)
    defer l1Cancel()

    for i := 0; i < 3; i++ {
        all.Add(1)
        go level2(l1Ctx, all)
    }

    for {
        select {
        case <-parent.Done():
            return
        // other cases if any,
        // this is a sample
        case <-time.After(time.Second):
            log.Println(`level1`)
        }
    }
}

func level2(parent context.Context, all *sync.WaitGroup) {
    defer all.Done()
    for {
        select {
        case <-parent.Done():
            return
        case <-time.After(time.Second):
            log.Println(`level2`)
        }
    }
}

Which gives us some output like:

[  info ] level2
[  info ] level2
[  info ] level2
[  info ] level1
[  info ] level2
[  info ] level1
[  info ] level2
[  info ] level2

Currently there is no official package that provide a functionality which combines context.Context and sync.WaitGroup. The nearest thing is an errgroup which can resemble this functionality with some hacks.

Kaveh Shahbazian
  • 13,088
  • 13
  • 80
  • 139