In Golang channels is similar to pipes in bash (|
). But in contrast to bash pipes which are used to transport output of one command to input of another command, Go channels are used to transport some data between goroutines. You can read more about channels here.
Channels have capacity. When you don't specify capacity for the channel go assumes that it has 0 capacity. Channels with zero capacity often called unbuffered
channels while channels with non-zero capacity called buffered
. When channel is full (number of elements in channel is equal to channel's capacity) than all write operations on the channel (->errs
) block execution flow until read operation (<-errs
) will be presented.
In your particular example you have unbuffered channel (the channel with 0 capacity). Thus any write operation (->errs
) on your channel will block the execution until some read operation would be provided, therefore all goroutines that you launched will be blocked despite the only one goroutine that would be able to proceed write operation when the flow of the main
function moved forward to read operation (err = <-errs
).
To solve your issue you could create one extra goroutine that would read from channel concurrently with goroutines that would write to channel. It will look like that:
func init() {
rand.Seed(1500929006430687579)
}
func goroutine(n int, wg *sync.WaitGroup, ch chan error) {
defer fmt.Println("defer done")
defer wg.Done()
fmt.Println("num ", n)
if n == 1 {
ch <- fmt.Errorf("error")
}
}
func main() {
var wg sync.WaitGroup
errs := make(chan error)
platforms := 2
types := 3
go func() {
for e := range errs {
fmt.Println(e)
}
}()
for j := 0; j < platforms; j++ {
for k := 0; k < types; k++ {
wg.Add(1)
n := rand.Intn(2)
go goroutine(n, &wg, errs)
}
for k := 0; k < types; k++ {
wg.Add(1)
n := rand.Intn(2)
go goroutine(n, &wg, errs)
}
}
wg.Wait()
}
In addition you have several bugs and inaccuracies that I refactored in your code:
- You shouldn't write nil in channel with errors. If you want
errs
chan to comprise only errors so write there only if your function executed with non-nil error.
- You had one extra wd.Add(1) as the beginning of
j
loop so there was disbalance between Add
functions and Done
function. 3.
- Furthemore, you add
defer fmt.Println("defer done")
after defer wg.Done()
but defer
s constructions are executed in reversed order than they were specified so it would be more correct to put defer fmt.Println("defer done")
before defer wg.Done()
so that "defer done" would really signalize that all previous defer
s had been executed.