Каждый раз, когда вы звоните recv1Block()
, он создает новый канал и запускает фоновую процедуру, которая пытается прочитать все данные с него. Поскольку вы вызываете его в цикле, у вас будет много вещей, которые будут пытаться использовать данные из канала; поскольку канал никогда не закрывается, все программы будут работать вечно.
В качестве упражнения вы можете попробовать изменить код так, чтобы он пропускал chan int
вместо chan struct{}
, и записывал серии последовательных чисел и распечатывал их по мере их получения. Такая последовательность действительна:
run
при вызове № 1 recv1Block()
.
recv1Block()
на GR # 1 порождает GR # 2 и возвращает канал № 2.
run
на блоках GR # 1, получающих на канале # 2.
recv1Block()
на GR # 2 читает 0
от w.c1
.
recv1Block()
на GR # 2 записывает 0
в канал # 2 (где run
на GR # 1 готов к чтению).
recv1Block()
на GR # 2 читает 1
от w.c1
.
recv1Block()
на GR # 2 хочет записать 0
на канал # 2, но блокирует.
run
на GR # 1 просыпается и получает 0
.
run
на вызовах GR # 1 recv1Block()
.
recv1Block()
на GR # 1 порождает GR # 3 и возвращает канал № 3.
recv1Block()
на GR # 3 читает 2
от w.c1
.
- ...
Обратите внимание, что значение 1 в этой последовательности никогда нигде не будет записано, и на самом деле не осталось ничего, что могло бы его прочитать.
Простое решение здесь состоит в том, чтобы не вызывать функцию создания канала в цикле:
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:
}
}
Это также стандартная практика закрывать каналы, когда вы закончите с ними. Это завершит цикл for ... range ch
и будет выглядеть как читаемое для оператора select
. В вашей функции генератора верхнего уровня:
for i := 0; i < w.limit; i++ {
w.ch1 <- struct{}{}
}
close(w.ch1)
А в вашей функции "копировать канал":
func (w *waiter) recv1Block() chan struct{} {
ch := make(chan struct{})
go func() {
for m := range w.ch1 {
ch <- m
}
close(ch)
}()
return ch
}
Это также означает, что вам не нужно запускать основной цикл «мертвым расчетом», ожидая, что он произведет ровно 100 элементов, а затем остановится; Вы можете остановиться, когда выходит его канал. Цикл потребления, который я показываю выше, делает это.