1 回答
TA贡献1895条经验 获得超3个赞
每次调用时recv1Block()
,它都会创建一个新通道并启动一个后台 goroutine,该 goroutine 会尝试从中读取所有数据。由于您是在循环中调用它,因此您将有很多东西都在尝试使用通道中的数据;由于通道永远不会关闭,所有的 goroutines 将永远运行。
作为练习,您可以尝试更改代码以传递 achan int
而不是 a chan struct{}
,并编写一系列序列号,并在最终收到时将它们打印出来。像这样的序列是有效的:
run
在 goroutine #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 个项目然后停止;只要它的频道退出,你就可以停止。我上面显示的消费者循环就是这样做的。
- 1 回答
- 0 关注
- 123 浏览
添加回答
举报