Every time you call recv1Block()
, it creates a new channel and launches a background goroutine that tries to read all of the data from it. Since you're calling it in a loop, you will have many things all trying to consume the data from the channel; since the channel never closes, all of the goroutines will run forever.
As an exercise, you might try changing your code to pass around a chan int
instead of a chan struct{}
, and write a series of sequential numbers, and print them out as they're ultimately received. A sequence like this is valid:
-
run
on goroutine #1 calls recv1Block()
.
-
recv1Block()
on GR#1 spawns GR#2, and returns channel#2.
-
run
on GR#1 blocks receiving on channel#2.
-
recv1Block()
on GR#2 reads 0
from w.c1
.
-
recv1Block()
on GR#2 writes 0
to channel#2 (where run
on GR#1 is ready to read).
-
recv1Block()
on GR#2 reads 1
from w.c1
.
-
recv1Block()
on GR#2 wants to write 0
to channel#2 but blocks.
-
run
on GR#1 wakes up, and receives the 0
.
-
run
on GR#1 calls recv1Block()
.
-
recv1Block()
on GR#1 spawns GR#3, and returns channel #3.
-
recv1Block()
on GR#3 reads 2
from w.c1
.
- ...
Notice that the value 1 in this sequence will never be written anywhere, and in fact there is nothing left that could read it.
The easy solution here is to not call the channel-creating function in a loop:
func (w *waiter) runBlock(wg *sync.WaitGroup) {
defer wg.Done()
ch1 := w.recv1Block()
ch2 := w.recv2()
for {
select {
case _, ok := <-ch1:
if !ok {
return
}
w.count++
case <-ch2:
}
}
It's also standard practice to close channels when you're done with them. This will terminate a for ... range ch
loop, and it will appear as readable to a select
statement. In your top-level generator function:
for i := 0; i < w.limit; i++ {
w.ch1 <- struct{}{}
}
close(w.ch1)
And in your "copy the channel" function:
func (w *waiter) recv1Block() chan struct{} {
ch := make(chan struct{})
go func() {
for m := range w.ch1 {
ch <- m
}
close(ch)
}()
return ch
}
This also means that you don't need to run the main loop by "dead reckoning", expecting it to produce exactly 100 items then stop; you can stop whenever its channel exits. The consumer loop I show above does this.