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?
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)
}