为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

Go
HUX布斯 2023-06-01 15:10:23
go 版本 go1.11.4 darwin/amd64创建了一个新的channel和goroutine,通过goroutine将旧channel的内容传输到new 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会被block。
查看完整描述

1 回答

?
蛊毒传说

TA贡献1895条经验 获得超3个赞

每次调用时recv1Block(),它都会创建一个新通道并启动一个后台 goroutine,该 goroutine 会尝试从中读取所有数据。由于您是在循环中调用它,因此您将有很多东西都在尝试使用通道中的数据;由于通道永远不会关闭,所有的 goroutines 将永远运行。

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

  1. run在 goroutine #1 上调用recv1Block()

  2. recv1Block()在 GR#1 上生成 GR#2,并返回通道#2。

  3. run在 GR#1 上阻止在通道#2 上接收。

  4. recv1Block()在 GR#2 上读取0w.c1.

  5. recv1Block()在 GR#2 上写入0通道#2(run在 GR#1 上准备好读取)。

  6. recv1Block()在 GR#2 上读取1w.c1.

  7. recv1Block()GR#2 上想要写入0通道#2 但阻塞了。

  8. runGR#1 上的唤醒,并接收到0

  9. run在 GR#1 电话上recv1Block()

  10. recv1Block()在 GR#1 上生成 GR#3,并返回通道 #3。

  11. recv1Block()在 GR#3 上读取2w.c1.

  12. ...

请注意,此序列中的值 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 个项目然后停止;只要它的频道退出,你就可以停止。我上面显示的消费者循环就是这样做的。


查看完整回答
反对 回复 2023-06-01
  • 1 回答
  • 0 关注
  • 123 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信