go版本go1.11.4 darwin/amd64
创建了一个新的channel和goroutine,通过goroutine将旧channel的内容传输到新的channel。应该不会阻塞,但是经过测试发现是阻塞了。
谢谢。
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
}
为什么 recv1Block
会被阻塞。
最佳答案
每次您调用 recv1Block()
时,它都会创建一个新 channel 并启动一个试图从中读取所有数据的后台 goroutine。由于您是在循环中调用它,因此您将有很多东西都在尝试使用 channel 中的数据;由于 channel 永远不会关闭,所有的 goroutines 将永远运行。
作为练习,您可以尝试更改代码以传递 chan int
而不是 chan struct{}
,并编写一系列序号,然后在最终收到时将它们打印出来。像这样的序列是有效的:
run
在 goroutine #1 上调用recv1Block()
。recv1Block()
在 GR#1 上生成 GR#2,并返回 channel #2。在 GR#1 上运行
会阻止在 channel#2 上接收。
GR#2 上的 recv1Block()
从w.c1
读取0
。
GR#2 上的 recv1Block()
将0
写入 channel #2(其中 GR#1 上的run
已准备好读取)。recv1Block()
在 GR#2 上从w.c1
读取1
。
GR#2 上的 recv1Block()
想要将0
写入 channel #2 但阻塞了。run
在 GR#1 上唤醒,并收到0
。run
在 GR#1 上调用recv1Block()
。recv1Block()
在 GR#1 上生成 GR#3,并返回 channel #3。recv1Block()
在 GR#3 上从w.c1
读取2
。- ...
请注意,此序列中的值 1 永远不会被写入任何地方,事实上,没有任何东西可以读取它。
这里最简单的解决方案是不要在循环中调用 channel 创建函数:
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:
}
}
完成 channel 后关闭 channel 也是标准做法。这将终止 for ... range ch
循环,并且它对于 select
语句将显示为可读。在您的顶级生成器函数中:
for i := 0; i < w.limit; i++ {
w.ch1 <- struct{}{}
}
close(w.ch1)
并且在您的“复制 channel ”功能中:
func (w *waiter) recv1Block() chan struct{} {
ch := make(chan struct{})
go func() {
for m := range w.ch1 {
ch <- m
}
close(ch)
}()
return ch
}
这也意味着您不需要通过“航位推算”运行主循环,期望它恰好产生 100 个项目然后停止;只要它的 channel 退出,你就可以停止。我上面显示的消费者循环就是这样做的。
关于go - Channel 有一个奇怪的行为,为什么要阻止?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54191527/