Changing channel from unbuffered to buffered prevents goroutine from running

Issue

Here is an exercise using channels and select in a goroutine. If the disconnect channel is changed to a buffered channel the goroutine doesn’t run at all.

Why does changing from an unbuffered to a buffered channel prevent running the goroutine?

func SelectDemo(wg *sync.WaitGroup) {

    messageCh := make(chan int, 10)
    disconnectCh := make(chan struct{})
    //  go routine won't run if channel is buffered
    //disconnectCh := make(chan struct{}, 1)

    defer close(messageCh)
    defer close(disconnectCh)
    go func() {
        fmt.Println("  goroutine")
        wg.Add(1)
        for {
            select {
            case v := <-messageCh:
                fmt.Println(v)
            case <-disconnectCh:
                fmt.Println("  disconnectCh")
                //  empty the buffered channel before exiting
                for {
                    select {
                    case v := <-messageCh:
                        fmt.Println(v)
                    default:
                        fmt.Println("  disconnection, return")
                        wg.Done()
                        return
                    }
                }
            }
        }
    }()

    fmt.Println("Sending ints")
    for i := 0; i < 10; i++ {
        messageCh <- i
    }

    fmt.Println("Sending done")
    disconnectCh <- struct{}{}
}

Here’s the code to call the function from main. I use the wait group to assure that the goroutine completes before the program exits:

wg := sync.WaitGroup{}
ch09.SelectDemo(&wg)
wg.Wait()

Solution

That code logic has many flaws – some of them are:
1- Since the messageCh is buffered, this code is not blocking:

    for i := 0; i < 10; i++ {
        messageCh <- i
    }

so the next code is in the fast path to run:

disconnectCh <- struct{}{}

if you make the disconnectCh buffered, this line runs without blocking too, and the SelectDemo function may exit befor running the wg.Add(1).

So: You must put:

wg.Add(1)

before

go func() {

2- Even with wg.Add(1) before go func() { code
you have:

    defer close(messageCh)
    defer close(disconnectCh)

which will close both channels at SelectDemo function return
And this select is a random selection since both channels are ready:

fmt.Println("  goroutine")
        for {
            select {
            case v := <-messageCh:
                fmt.Println(v)
            case <-disconnectCh:

and it is highly likely that the second select:

                for {
                    select {
                    case v := <-messageCh:
                        fmt.Println(v)
                    default:
                        fmt.Println("  disconnection, return")
                        wg.Done()
                        return
                    }
                }

will run forever since the messageCh is closed, returning 0 forever after channel data read:

case v := <-messageCh:
    fmt.Println(v)

Answered By – wasmup

Answer Checked By – Cary Denson (GoLangFix Admin)

Leave a Reply

Your email address will not be published.