go - Channel 有一个奇怪的行为,为什么要阻止?

标签 go

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{},并编写一系列序号,然后在最终收到时将它们打印出来。像这样的序列是有效的:

  1. run 在 goroutine #1 上调用 recv1Block()
  2. recv1Block() 在 GR#1 上生成 GR#2,并返回 channel #2。
  3. 在 GR#1 上运行会阻止在 channel#2 上接收。
  4. GR#2 上的
  5. recv1Block()w.c1 读取 0
  6. GR#2 上的
  7. recv1Block()0 写入 channel #2(其中 GR#1 上的 run 已准备好读取)。
  8. recv1Block() 在 GR#2 上从 w.c1 读取 1
  9. GR#2 上的
  10. recv1Block() 想要将 0 写入 channel #2 但阻塞了。
  11. run 在 GR#1 上唤醒,并收到 0
  12. run 在 GR#1 上调用 recv1Block()
  13. recv1Block() 在 GR#1 上生成 GR#3,并返回 channel #3。
  14. recv1Block() 在 GR#3 上从 w.c1 读取 2
  15. ...

请注意,此序列中的值 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/

相关文章:

go - Apache ignite SQL 插入失败

go - 使 marshar 从字符串失败

go - 堆叠写入器和 zlib.writer 的校验和错误

amazon-web-services - 由AWS Presign函数生成的URL不起作用

go - 使用值接收器存储新值

go - 如何增加 golang.org/x/crypto/ssh 的详细程度

linux - 为什么这个 twurl 命令(在 linux 上运行,由 golang exec 运行)没有经过身份验证?

goroutine 等待 channel 的响应并继续

go - 如何在连接关闭时终止正在运行的查询

go - 为整个项目生成 godoc 文档?