【问题标题】:Why are slices of directional channels failing to compile?为什么定向通道切片无法编译?
【发布时间】:2021-09-26 15:05:20
【问题描述】:

https://play.golang.org/p/-cHBgiNl0tK

package main

import (
    "fmt"
    "time"
)

func read(ch ...<-chan int) {
    for i := 0; i < len(ch); i++ {
        i := i
        go func() { fmt.Println(<-ch[i]) }()
    }
}

func write(ch ...chan<- int) {
    for i := 0; i < len(ch); i++ {
        i := i
        go func() { ch[i] <- i }()
    }
}

func main() {
    var maxLen = 10
    var ch []chan int
    for i := 0; i < maxLen; i++ {
        ch = append(ch, make(chan int))
    }
    read(ch...)
    write(ch...)
    time.Sleep(10 * time.Second)
}

上面的 sn-p 失败并出现以下错误

./prog.go:28:6: cannot use ch (type []chan int) as type []<-chan int in argument to read
./prog.go:29:7: cannot use ch (type []chan int) as type []chan<- int in argument to write

如何使其与readwrite 函数中的通道方向一起使用?

【问题讨论】:

  • 该语言会将双向通道参数转换为定向通道,但不会将双向通道切片转换为定向通道切片。
  • @HymnsForDisco 明白你的意思,但有没有办法可以将比迪通道切片转换为定向通道?
  • 使这种转换合法化的现有提案:github.com/golang/go/issues/41695
  • @HymnsForDisco 不错的发现。您应该在答案中添加指向提案的链接。

标签: go slice channel


【解决方案1】:

虽然chan int 类型的表达式可以分配给chan&lt;- int(仅发送通道)或&lt;-chan int(仅接收通道)类型的变量,但同样不适用于此类类型的切片:

var (
    ch chan int
    _  <-chan int   = ch // ok
    _  chan<- int   = ch // ok
    _  []<-chan int = []chan int(nil) // compilation error
    _  []chan<- int = []chan int(nil) // compilation error
)

(Playground)

./prog.go:7:2: cannot use ([]chan int)(nil) (type []chan int) as type []<-chan int in assignment
./prog.go:8:2: cannot use ([]chan int)(nil) (type []chan int) as type []chan<- int in assignment

As pointed out by Hymns for Disco in his/her answer,不幸的是没有简单的出路。我不建议自己使用unsafe 包,因为与unsafe 相关的风险会抵消使用通道方向的好处。我只需将参数中的通道方向放到函数writeread 中。 YMMV。

【讨论】:

    【解决方案2】:

    您正在尝试将[]chan int 作为[]&lt;-chan int 传递。看起来像一个简单的转换,但它实际上是一个 deep 转换,因为您正在更改容器的 元素类型。这些转换在 Go 中是不允许的,尽管有一个现有的(关闭的)提议允许这种通道切片转换:https://github.com/golang/go/issues/41695

    这是我过去在类似情况下使用过的解决方案:

    func readOnlyChannels(slice []chan int) []<-chan int {
        return *(*[]<-chan int)(unsafe.Pointer(&slice))
    }
    
    func writeOnlyChannels(slice []chan int) []chan<- int {
        return *(*[]chan<- int)(unsafe.Pointer(&slice))
    }
    

    然后用法:

        read(readOnlyChannels(ch)...)
        write(writeOnlyChannels(ch)...)
    

    由于通道的只读或只写质量仅在编译时的类型信息中携带,而不是在类型[需要引用]的实际数据表示中,您可以使用unsafe 来强制转换。


    注意:我不知道有任何明确或隐含的保证 unsafe 转换是有效的,因此您应该创建测试来验证它是否适用于您的目标平台。然而,在实践中,它似乎奏效了。它依赖于具有相同(或至少兼容)数据表示的不同方向的通道值。实际上,没有理由说明通道方向性的设计需要在运行时保持通道方向,甚至可以从中受益。

    如果你想完全避免unsafe,那么你必须像这样进行“长”转换:

    func readOnlyChannels(slice []chan int) []<-chan int {
        out := make([]<-chan int, len(slice))
        for i := range slice {
            out[i] = slice[i]
        }
        return out
    }
    
    func writeOnlyChannels(slice []chan int) []chan<- int {
        out := make([]chan<- int, len(slice))
        for i := range slice {
            out[i] = slice[i]
        }
        return out
    }
    

    这里唯一真正的缺点是,当您进行转换时,它会通过将所有内容复制到新切片中来导致更多的分配。使用对您的应用程序造成最少问题的方法。

    【讨论】:

    • 我宁愿放弃方向(如果函数 readwrite 在我的控制之下)而不是诉诸 unsafe 包。
    • @jub0bs 有道理。我过去使用它的时候是当通道方向性是接口/函数类型的一部分时,它打算在不同的地方实现。在这种情况下,权衡取舍可能是值得的。
    • 我喜欢 [需要引用] 注释。 :-) 这都是真的!
    • 很好,并且 +1 了。我很想看到 [引文需要] 得到解决。
    【解决方案3】:

    支持定向通道的通道通道的替代方法。

    package main
    
    import (
        "fmt"
        "time"
    )
    
    func read(inputs chan (<-chan int)) {
        for input := range inputs {
            go func(input <-chan int) { fmt.Println(<-input) }(input)
        }
    }
    
    func write(outputs chan chan<- int) {
        i := 0
        for output := range outputs {
            go func(output chan<- int, i int) { output <- i }(output, i)
            i += 1
        }
    }
    
    func main() {
        var maxLen = 10
        readers := make(chan (<-chan int), 10)
        writers := make(chan chan<- int, 10)
        for i := 0; i < maxLen; i++ {
            ch := make(chan int)
            readers <- ch
            writers <- ch
        }
        close(readers)
        close(writers)
        read(readers)
        write(writers)
        time.Sleep(10 * time.Second)
    }
    

    【讨论】:

    • 外部渠道给你买什么?难道readerswriters 不能简单地属于[]&lt;-chan int[]chan&lt;- int(分别)吗?
    • 没有扩展要求什么都没有。您的解决方案也将起作用。它更加优雅。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-26
    • 2014-07-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多