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.