go version go1.11.4 darwin/amd64
A new channel and goroutine were created, and the content of the old channel was transferred to the new channel through goroutine. It should not block, but after testing, it was found to be blocked.
thanks.
type waiter struct {
ch1 chan struct{}
ch2 <-chan time.Time
limit int
count int
}
func (w *waiter) recv1Block() chan struct{} {
ch := make(chan struct{})
go func() {
for m := range w.ch1 {
ch <- m
}
}()
return ch
}
func (w *waiter) runBlock(wg *sync.WaitGroup) {
defer wg.Done()
i := 0
for i < w.limit {
select {
case <-w.recv1Block(): **// why block here?**
i++
case <-w.recv2():
}
}
w.count = i
}
why recv1Block
will be block.
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.