'GO routine never exits based on stop condition - unable to find the reason

In this example, we have a worker. The idea here is simulate clean shutdown of all go routines based on a condition.

In this case, go routines get spun - based on workers count. Each go routine reads the channel, does some work and sends output to the outputChannel.

The main go routine reads this output and prints it. To simulate a stop condition, the doneChannel is closed. Expected outcome is that select inside each go routine will pick this up and execute return which in turn will call the defer println. The actual output is that it never gets called and main exits.

Not sure what's the reason behind this.

package main

import (
    "log"
    "time"
)

const jobs = 100
const workers = 1

var timeout = time.After(5 * time.Second)

func main() {

    doneChannel := make(chan interface{})
    outputChannel := make(chan int)

    numberStream := generator()

    for i := 1; i <= workers; i++ {
        go worker(doneChannel, numberStream, outputChannel)
    }

    // listen for output
loop:
    for {
        select {
        case i := <-outputChannel:
            log.Println(i)
        case <-timeout:
            // before you timeout cleanup go routines
            break loop
        }
    }

    close(doneChannel)
    time.Sleep(5 * time.Second)
    log.Println("main exited")

}

func generator() <-chan int {
    defer log.Println("generator completed !")
    c := make(chan int)
    go func() {
        for i := 1; i <= jobs; i++ {
            c <- i
        }

        defer close(c)
    }()
    return c
}

func worker(done <-chan interface{}, c <-chan int, output chan<- int) {
    // this will be a go routine
    // Do some work and send results to output Channel.
    // Incase if the done channel is called kill the go routine.

    defer log.Println("go routines exited")

    for {
        select {
        case <-done:
            log.Println("here")
            return
        case i := <-c:
            time.Sleep(1 * time.Second) // worker delay
            output <- i * 100

        }
    }

}
go


Solution 1:[1]

When your main loop finishes during the timeout, you continue your program and

  1. Close done channel
  2. Print message
  3. Exit

There is no reason to wait for any goroutine to process the signal of this channel.

If you add a small sleep you will see some messages

In real scenarios we use a waitgroup to be sure all goroutine finish properly

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Tiago Peczenyj