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

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。


HUX布斯
浏览 124回答 1
1回答

蛊毒传说

每次调用时recv1Block(),它都会创建一个新通道并启动一个后台 goroutine,该 goroutine 会尝试从中读取所有数据。由于您是在循环中调用它,因此您将有很多东西都在尝试使用通道中的数据;由于通道永远不会关闭,所有的 goroutines 将永远运行。作为练习,您可以尝试更改代码以传递 achan int而不是 a&nbsp;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 但阻塞了。runGR#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) {&nbsp; &nbsp; defer wg.Done()&nbsp; &nbsp; ch1 := w.recv1Block()&nbsp; &nbsp; ch2 := w.recv2()&nbsp; &nbsp; for {&nbsp; &nbsp; &nbsp; &nbsp; select {&nbsp; &nbsp; &nbsp; &nbsp; case _, ok := <-ch1:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if !ok {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; w.count++&nbsp; &nbsp; &nbsp; &nbsp; case <-ch2:&nbsp; &nbsp; }}完成频道后关闭频道也是标准做法。这将终止一个for ... range ch循环,并且它看起来对select语句是可读的。在您的顶级生成器函数中:for i := 0; i < w.limit; i++ {&nbsp; &nbsp; w.ch1 <- struct{}{}}close(w.ch1)在您的“复制频道”功能中:func (w *waiter) recv1Block() chan struct{} {&nbsp; &nbsp; ch := make(chan struct{})&nbsp; &nbsp; go func() {&nbsp; &nbsp; &nbsp; &nbsp; for m := range w.ch1 {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ch <- m&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; close(ch)&nbsp; &nbsp; }()&nbsp; &nbsp; return ch}这也意味着您不需要通过“航位推算”运行主循环,期望它正好产生 100 个项目然后停止;只要它的频道退出,你就可以停止。我上面显示的消费者循环就是这样做的。
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Go