为什么定向通道切片无法编译?

Why are slices of directional channels failing to compile?

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)
}

以上代码段失败并出现以下错误

./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 函数中使用通道方向?

尽管 chan int 类型的表达式可分配给 chan<- int(仅发送通道)或 <-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

,不幸的是没有简单的出路。我不建议自己使用 unsafe 包,因为与 unsafe 相关的风险抵消了使用通道方向的好处。我会简单地将参数中的通道方向放到函数 writeread 中。 YMMV.

您正在尝试将 []chan int 作为 []<-chan int 传递。看似简单的转换,但实际上是 深度 转换,因为您正在更改容器的 元素类型 。这些转换在 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
}

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

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

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)
}