goconcurrencydeadlockchannelrace-condition

Go channel sometimes not receiving the last value


I'm currently learning go channels, and I'm trying out this piece of code. It creates 10 goroutines which sends a 1000 1s each to a channel. Then another go routine receives it and adds it to a counter. I also used WaitGroups to make sure the goroutines are finished before printing the results.

Code:

package main

import (
    "fmt"
    "runtime"
    "sync"
    "time"
)

func main() {
    var counter int

    fmt.Println("\nWithout Channels -------")
    for i := 0; i < 10; i++ {
        go func() {
            for j := 0; j < 1000; j++ {
                // to simulate race condition
                time.Sleep(time.Duration(1))
                counter++
            }
        }()
    }

    runtime.Gosched()

    fmt.Println("Expected counter: 10000, Actual counter:", counter)

    fmt.Println("\nWith Channels -------")
    for i := 0; i < 100; i++ {
        WithChannels()
    }
}

func WithChannels() {
    var counter int
    ch := make(chan int)
    var wg sync.WaitGroup
    // var wg2 sync.WaitGroup

    wg.Add(10)
    for i := 0; i < 10; i++ {
        go func() {
            defer wg.Done()
            for j := 0; j < 1000; j++ {
                time.Sleep(time.Duration(1))
                ch <- 1
            }
        }()
    }

    // wg2.Add(1)
    go func() {
        for {
            increment, ok := <-ch
            if !ok {
                // channel closed, break
                break
            }
            counter += increment
        }
        // wg2.Done()
    }()

    wg.Wait()
    // wg2.Wait()
    // time.Sleep(time.Duration(1) * time.Second)
    fmt.Println("Expected counter: 10000, Actual counter:", counter)
    close(ch)
}

Output:

Without Channels -------
Expected counter: 10000, Actual counter: 139

With Channels -------
Expected counter: 10000, Actual counter: 9999
Expected counter: 10000, Actual counter: 10000
Expected counter: 10000, Actual counter: 9999
Expected counter: 10000, Actual counter: 10000
Expected counter: 10000, Actual counter: 10000
Expected counter: 10000, Actual counter: 9999
...

I expect the counter be 10,000 every time I run it, however it seems like sometimes it is not receiving the last value. I've tried adding another WaitGroup for the receiving goroutine (seen in comments), but it causes a deadlock.

Am I doing something wrong here?


Solution

  • You're receiving from the channel in a goroutine but never waiting for it to finish its work. You could instead use a goroutine for waiting on the completion of the senders and closing the channel, and then just receive from the channel in the main goroutine.

    func WithChannels() {
        var counter int
        ch := make(chan int)
        var wg sync.WaitGroup
    
        for i := 0; i < 10; i++ {
            wg.Add(1)
            go func() {
                defer wg.Done()
                for j := 0; j < 1000; j++ {
                    time.Sleep(time.Duration(1))
                    ch <- 1
                }
            }()
        }
    
        // Use a goroutine to wait for all the senders to complete and then close the channel.
        go func() {
            wg.Wait()
            close(ch)
        }()
    
        // Receive from the channel until it's closed.
        for {
            increment, ok := <-ch
            if !ok {
                // channel closed, break
                break
            }
            counter += increment
        }
        
        fmt.Println("Expected counter: 10000, Actual counter:", counter)
    }