在单个“选择”情况下的链接通道操作

时间:2018-07-04 07:20:01

标签: go

我尝试多路复用2个通道A和B。A发送的延迟为10毫秒,而B为1秒。我使用select等待A和B,然后将结果发送到扇入通道,然后在main中接收值。

package main

import  (
"fmt"
"time"
)

func talk(msg string, wait_time int) <-chan string {
    ch := make(chan string)
    go func () {
        for i:=0;i<5;i++ {
            ch <- fmt.Sprintf("%s %d", msg, i)
            time.Sleep(time.Duration(wait_time)*time.Millisecond)
        }
    }()

    return ch
}

func fanIn(input1, input2 <-chan string) <-chan string {
    ch := make(chan string)
    go func () {
        for {
            select {
                case t :=<-input1:
                    ch <- t
                case t := <-input2:
                    ch <- t
            }
        }
    }()

    return ch
}
func main() {
    ch := fanIn(talk("A", 10), talk("B", 1000))

    for i:=0; i<10; i++ {
        fmt.Printf("%q\n", <-ch)
    }
    fmt.Printf("Done\n")

}

这将获得如下所示的正确结果

"A 0"
"B 0"
"A 1"
"A 2"
"A 3"
"A 4"
"B 1"
"B 2"
"B 3"
"B 4"
Done

我的问题是,当我更改案例陈述时,我得到了奇怪的输出。似乎有些值下降了,当然在扇入通道中没有收到更多的值并且发生了死锁。

select {
                case ch<- <-input1:
                case ch<- <-input2:
            }

结果是这样的:

"B 0"
"A 1"
"B 2"
"A 3"
"A 4"
fatal error: all goroutines are asleep - deadlock!

有人知道这种情况吗?

3 个答案:

答案 0 :(得分:6)

之所以发生这种情况,是因为在选择中,只有一个通道的读取或写入是非阻塞的。

所有其他操作均正常。

所以在这段代码中

select {
                case ch<- <-input1:
                case ch<- <-input2:
            }

它从input1接收一个值(阻止)。它等待延迟并收到A 0

它将尝试将其写入ch,并且不会阻塞。

如果main中的代码足够快达到

 fmt.Printf("%q\n", <-ch)

行然后成功写入通道。

然后fanIn for循环开始第二次迭代:这次选择第二个case(不确定)。此时,第二个goroutine可能已经写入了B 0值。

但是main函数循环有可能没有消耗组合通道中的值。

因此该值被删除。

这会重复多次,您丢失的值很少,最终没有编写器,而读取器将永远等待剩余的值。

此经过稍微修改的代码副本演示了它:https://play.golang.org/p/lcM5OKx09Dj

答案 1 :(得分:2)

我将解释为什么您在渠道中的价值被降低了。 基于Golang文档中有关执行select语句的事实:

  

对于该语句中的所有情况,输入“ select”语句后,将按源顺序对接收操作的通道操作数以及send语句的通道表达式和右侧表达式进行一次精确评估。结果是一组要从中接收或发送到的通道,以及要发送的相应值。不管选择进行哪个通信操作,都会发生该评估中的任何副作用。具有简短变量声明或赋值的RecvStmt左侧的表达式尚未评估。

结果是一组接收或发送到的通道,因此,当您在左侧传递通道时,请先使用select语句尝试获取 ALL 右侧语句在尝试发送到左侧语句中的通道之前先进行评估

很显然,必须在一轮选择语句中接收到input1和input2,然后将其发送到ch,因此ch通道始终具有正好5的值,该值与intput1和input2混合在一起 ,尽管延迟了

在此代码中向input1和input2通道发送值时,您可以看到所有数据已打印: Link

答案 2 :(得分:1)

(我读过这篇文章,我还没有在GO文档中看到这种'case'子句的特定组合的具体解释)。

当您的“选择”中包含涉及多个渠道的“案例”子句时,GO会选择其中的任何个,并将其作为“唤醒选择的渠道”

在您的第一个代码段中,“ case”子句仅引用input1和input2(正确的-您实际想要的内容,即“等待input1或input2”)。

在第二个代码段中,“ case”子句中同时包含三个(import xml.etree.ElementTree as ET root = ET.fromstring('<parent><child></child></parent>') child = root.find('child') parent = child.find('..') # retrieve the parent parent is None # unexpected answer True input1input2),因此该语句显示为“等待chinput1可读或input2可写”。现在,ch可以由于主要等待而变得易于写入,因此您的“选择”可以在那醒来,并最终对input1或intput2进行读取,而此时它们却无法被读取(不是您想要的内容) )。