频道行为异常,为什么要阻止?

时间:2019-01-15 01:27:06

标签: go

go版本go1.11.4 darwin / amd64

创建了一个新频道和goroutine,并且旧频道的内容已通过goroutine转移到了新频道。它不应该阻止,但是经过测试,发现它被阻止了。

谢谢。

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将被阻止。

1 个答案:

答案 0 :(得分:2)

每次调用recv1Block()

,它都会创建一个新通道并启动一个后台goroutine,尝试从中读取所有数据。由于您是在循环中调用它,因此您将有很多事情试图消耗通道中的数据。由于通道永远不会关闭,因此所有goroutine都将永远运行。

作为练习,您可以尝试将代码更改为绕过chan int而不是chan struct{},并写出一系列序号,并在最终收到时打印出来。这样的序列是有效的:

  1. run在例行程序1上调用recv1Block()
  2. {#1}在GR#1上生成GR#2,并返回channel#2。
  3. GR#1上的recv1Block()阻止了频道2上的接收。
  4. GR#2上的runrecv1Block()中读取0
  5. GR#2上的w.c1recv1Block()写入通道#2(其中GR#1上的0可以读取)。
  6. GR#2上的runrecv1Block()中读取1
  7. GR#2上的w.c1想将recv1Block()写入通道2,但会阻塞。
  8. {#1}在GR#1上唤醒,并收到0
  9. run在GR#1上调用0
  10. {#1}在GR#1上产生GR#3,并返回频道#3。
  11. GR#3上的runrecv1Block()中读取recv1Block()
  12. ...

请注意,此序列中的值1永远不会写入任何地方,实际上,没有任何东西可以读取它。

这里的简单解决方案是不循环调用频道创建函数:

recv1Block()

完成频道后,关闭频道也是一种标准做法。这将终止2循环,并且对w.c1语句可读。在您的顶级生成器函数中:

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

这也意味着您不需要通过“航位推算”来运行主循环,因为它可以精确地产生100个项目然后停止;您可以在频道退出时停止播放。我上面显示的消费者循环可以做到这一点。