By user5646735


2017-02-28 19:00:39 8 Comments

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?

3 comments

@Kaveh Shahbazian 2017-02-28 21:15:34

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.

@Sascha 2017-02-28 19:22:02

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.

@user5646735 2017-02-28 20:11:13

That's it! It runs, thank you!

@Kaedys 2017-02-28 20:13:48

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: 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.

@Dean Elbaz 2017-02-28 19:08:08

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)

Related Questions

Sponsored Content

6 Answered Questions

[SOLVED] Always have x number of goroutines running at any time

  • 2014-08-14 10:40:47
  • Alasdair
  • 9336 View
  • 22 Score
  • 6 Answer
  • Tags:   go goroutine

2 Answered Questions

[SOLVED] How to stop a goroutine that is listening for RethinkDB changefeeds?

6 Answered Questions

[SOLVED] how to stop a goroutine

  • 2011-07-24 15:05:03
  • Łukasz Gruner
  • 79186 View
  • 74 Score
  • 6 Answer
  • Tags:   go goroutine channels

1 Answered Questions

[SOLVED] What is the right way to safely finish goroutines in my code?

1 Answered Questions

[SOLVED] How to stop goroutine blocked by external I/O started for process?

1 Answered Questions

[SOLVED] Ensuring goroutine cleanup, bestpractice

0 Answered Questions

Why is my goroutine starving?

1 Answered Questions

[SOLVED] Go GC stopping my goroutine?

4 Answered Questions

[SOLVED] Ticker Stop behaviour in Golang

  • 2013-07-22 21:28:53
  • whatupdave
  • 14145 View
  • 13 Score
  • 4 Answer
  • Tags:   go ticker

1 Answered Questions

[SOLVED] Go net listener closing and routine handling

Sponsored Content